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

---
 assets/js/concise/Referral.js |  508 +++++++++++++++++++++++++++++++++++---------------------
 1 files changed, 316 insertions(+), 192 deletions(-)

diff --git a/assets/js/concise/Referral.js b/assets/js/concise/Referral.js
index 6f2442d..9ec7702 100644
--- a/assets/js/concise/Referral.js
+++ b/assets/js/concise/Referral.js
@@ -1,7 +1,6 @@
 /**
  * Referral Widget Manager
  * Handles both logged-in share widget and public code validation widget
- *
  */
 
 class Referral {
@@ -12,41 +11,36 @@
 		}
 
 		this.a11y = window.jvbA11y;
-
 		this.toggle = document.querySelector('button[data-action="toggle-referral"]');
 
 		this.initElements();
 		this.initListeners();
 		this.checkForReferral();
+
+		// Load additional data for logged-in users
+		if (this.isLoggedIn()) {
+			this.loadStats();
+			this.loadRecentReferrals();
+		}
 	}
 
-	initElements()
-	{
+	initElements() {
 		this.selectors = {
-			copy: 'button.copy',
-			login: '.login',
+			copyBtn: '.copy-btn',
+			checkCode: '.check-code-btn',
 			submit: '[type=submit]',
 		};
 
 		this.forms = this.container.querySelectorAll('form');
-		console.log(this.forms);
 		this.popup = new window.jvbPopup({
 			toggle: this.toggle,
 			popup: this.container,
 			name: 'Referral Box',
 			onOpen: () => {
-				this.forms.forEach(form => {
-					form.addEventListener('submit', this.submitHandler);
-				});
-				this.container.addEventListener('click', this.clickHandler);
-				this.container.addEventListener('input', this.inputHandler);
+				this.bindEventListeners(true);
 			},
 			onClose: () => {
-				this.forms.forEach(form => {
-					form.removeEventListener('submit', this.submitHandler);
-				});
-				this.container.removeEventListener('click', this.clickHandler);
-				this.container.removeEventListener('input', this.inputHandler);
+				this.bindEventListeners(false);
 			}
 		});
 
@@ -62,43 +56,170 @@
 		this.clickHandler = this.handleClick.bind(this);
 		this.inputHandler = this.handleInput.bind(this);
 		this.submitHandler = this.handleFormSubmit.bind(this);
-		this.changeHandler = this.handleChange.bind(this);
+	}
 
+	bindEventListeners(bind) {
+		const method = bind ? 'addEventListener' : 'removeEventListener';
+
+		this.forms.forEach(form => {
+			form[method]('submit', this.submitHandler);
+		});
+
+		this.container[method]('click', this.clickHandler);
+		this.container[method]('input', this.inputHandler);
+	}
+
+	isLoggedIn() {
+		return Boolean(jvbSettings.currentUser);
 	}
 
 	handleClick(e) {
-		if (e.target.classList.contains('.copy')) {
-			let target = e.target.dataset.target;
-			let value = this.container.querySelector(`#${target}`);
-			value = (value) ? value.textContent : false;
-			if (value) {
-				this.handleCopy(e.target, value);
-			}
+		const target = e.target.closest('.copy-btn, .check-code-btn, .attn');
+		if (!target) return;
+
+		if (target.classList.contains('copy-btn')) {
+			this.handleCopyClick(target);
+		} else if (target.classList.contains('check-code-btn')) {
+			this.handleCheckCode(e);
+		} else if (target.classList.contains('attn')) {
+			target.classList.remove('attn');
 		}
 	}
 
-	handleChange(e) {
-		if (e.target.id === 'referral-code') {
-			window.debouncer.schedule(
-				'check-referral',
-				()=> this.makeRequest('referrals/check-code', {code: e.target.value})),
-				150
+	/**
+	 * Handle copy button click with fallback
+	 */
+	handleCopyClick(button) {
+		const targetId = button.dataset.target;
+		const codeElement = this.container.querySelector(`#${targetId}`);
+
+		if (!codeElement) return;
+
+		const text = codeElement.textContent.trim();
+
+		// Try clipboard API first
+		if (navigator.clipboard && navigator.clipboard.writeText) {
+			navigator.clipboard.writeText(text).then(() => {
+				this.showCopySuccess(button);
+			}).catch(() => {
+				// Fallback to selection
+				this.selectText(codeElement);
+				this.showCopyFallback(button);
+			});
+		} else {
+			// Fallback to selection
+			this.selectText(codeElement);
+			this.showCopyFallback(button);
 		}
 	}
 
+	/**
+	 * Select text in element
+	 */
+	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();
+		}
+	}
+
+	/**
+	 * Show copy success feedback
+	 */
+	showCopySuccess(button) {
+		const originalHTML = button.innerHTML;
+		button.innerHTML = window.jvbIcon('check', {size: 16}) + ' Copied!';
+		button.classList.add('success');
+
+		setTimeout(() => {
+			button.innerHTML = originalHTML;
+			button.classList.remove('success');
+		}, 2000);
+	}
+
+	/**
+	 * Show fallback message
+	 */
+	showCopyFallback(button) {
+		const originalHTML = button.innerHTML;
+		button.innerHTML = '✓ Selected - Press Ctrl+C';
+		button.classList.add('selected');
+
+		setTimeout(() => {
+			button.innerHTML = originalHTML;
+			button.classList.remove('selected');
+		}, 3000);
+	}
+
 	handleInput(e) {
-		if (e.target.id === 'referral-code') {
+		if (e.target.id === 'referral_code' || e.target.name === 'referral_code') {
 			e.target.value = e.target.value.toUpperCase();
 		}
 	}
 
-	// ==========================================
-	// SHARE WIDGET (Logged-In Users)
-	// ==========================================
+	/**
+	 * Handle code verification
+	 */
+	async handleCheckCode(e) {
+		e.preventDefault();
 
-	initShareWidget() {
-		this.initCopyButton();
-		this.loadStats();
+		const form = e.target.closest('form');
+		const codeInput = form.querySelector('[name="referral_code"]');
+		const statusDiv = form.querySelector('.code-status');
+
+		if (!codeInput || !statusDiv) return;
+
+		const code = codeInput.value.trim();
+
+		if (!code) {
+			this.showCodeStatus(statusDiv, 'Please enter a code', 'error');
+			return;
+		}
+
+		// Show loading
+		statusDiv.hidden = false;
+		statusDiv.className = 'code-status loading';
+		statusDiv.innerHTML = '<span class="spinner"></span> Checking...';
+
+		try {
+			const result = await this.validateCodeOnly(code);
+
+			if (result.success) {
+				this.showCodeStatus(
+					statusDiv,
+					`✓ Valid! Referred by ${result.referrer_name}`,
+					'success'
+				);
+			} else {
+				this.showCodeStatus(statusDiv, result.message || 'Invalid code', 'error');
+			}
+		} catch (error) {
+			console.error('Error checking code:', error);
+			this.showCodeStatus(statusDiv, 'Error checking code', 'error');
+		}
+	}
+
+	/**
+	 * Show code verification status
+	 */
+	showCodeStatus(statusDiv, message, type) {
+		statusDiv.hidden = false;
+		statusDiv.className = `code-status ${type}`;
+		statusDiv.textContent = message;
+
+		if (type === 'error') {
+			setTimeout(() => {
+				statusDiv.hidden = true;
+			}, 5000);
+		}
 	}
 
 	/**
@@ -107,15 +228,17 @@
 	async checkForReferral() {
 		const isLoggedIn = this.getUrlParameter('seeReferral');
 		const refCode = this.getUrlParameter('ref');
+
 		if (!isLoggedIn && !refCode) {
 			return;
 		}
+
 		if (!refCode) {
 			this.popup.openPopup();
 			return;
 		}
 
-		const codeInput = this.container.querySelector('#referral-code-input');
+		const codeInput = this.container.querySelector('[name="referral_code"]');
 		if (!codeInput) return;
 
 		// Convert to uppercase
@@ -123,25 +246,30 @@
 
 		// Pre-fill the code input
 		codeInput.value = code;
-		codeInput.readOnly = true; // Make it read-only since it came from link
+		codeInput.readOnly = true;
 
 		this.popup.togglePopup();
 
-		// Validate the code immediately to show referrer info
+		// Validate the code immediately
 		try {
 			const referrer = await this.validateCodeOnly(code);
 
 			if (referrer.success) {
-				// Show referrer info banner
-				this.showReferrerBanner(referrer.referrer_name, code);
+				const statusDiv = codeInput.closest('form').querySelector('.code-status');
+				if (statusDiv) {
+					this.showCodeStatus(
+						statusDiv,
+						`✓ ${referrer.referrer_name} invited you!`,
+						'success'
+					);
+				}
 
-				// Focus on name input (first empty field)
-				const nameInput = this.container.querySelector('#referral-name');
+				// Focus on name input
+				const nameInput = this.container.querySelector('[name="referral_name"]');
 				if (nameInput) {
 					nameInput.focus();
 				}
 			} else {
-				// Invalid code - make input editable and show error
 				codeInput.readOnly = false;
 				this.showMessage('This referral link is invalid. Please enter a valid code.', 'error');
 			}
@@ -150,21 +278,15 @@
 			codeInput.readOnly = false;
 		}
 
-		// Clean up URL (remove ?ref parameter)
+		// Clean up URL
 		this.removeUrlParameter('ref');
 	}
 
-	/**
-	 * Get URL parameter value
-	 */
 	getUrlParameter(name) {
 		const urlParams = new URLSearchParams(window.location.search);
 		return urlParams.get(name);
 	}
 
-	/**
-	 * Remove URL parameter (clean URL)
-	 */
 	removeUrlParameter(name) {
 		const url = new URL(window.location);
 		url.searchParams.delete(name);
@@ -172,13 +294,14 @@
 	}
 
 	/**
-	 * Validate code without registering (just check if valid)
+	 * Validate code without registering
 	 */
 	async validateCodeOnly(code) {
-		const response = await fetch(`${jvbSettings.api}/referrals/check-code`, {
+		const response = await fetch(`${jvbSettings.api}referrals/check-code`, {
 			method: 'POST',
 			headers: {
-				'Content-Type': 'application/json'
+				'Content-Type': 'application/json',
+				'X-WP-Nonce': jvbSettings.nonce
 			},
 			body: JSON.stringify({ code: code })
 		});
@@ -187,65 +310,14 @@
 	}
 
 	/**
-	 * Show banner with referrer info
+	 * Load user stats
 	 */
-	showReferrerBanner(referrerName, code) {
-		const header = this.container.querySelector('.referral-header');
-		if (!header) return;
-
-		// Create banner
-		const banner = document.createElement('div');
-		banner.className = 'referrer-banner';
-		banner.innerHTML = `
-            <div class="banner-icon">🎉</div>
-            <div class="banner-content">
-                <strong>${window.escapeHtml(referrerName)}</strong> referred you!
-                <div class="banner-code">Code: <code>${window.escapeHtml(code)}</code></div>
-            </div>
-        `;
-
-		// Insert after header
-		header.parentNode.insertBefore(banner, header.nextSibling);
-
-		// Update header text
-		const headerTitle = header.querySelector('h3');
-		if (headerTitle) {
-			headerTitle.textContent = 'Complete Your Registration';
-		}
-
-		const headerDesc = header.querySelector('p');
-		if (headerDesc) {
-			headerDesc.textContent = 'Enter your details below to claim your welcome reward!';
-		}
-	}
-
-	/**
-	 * Copy referral link to clipboard
-	 */
-	handleCopy(button, text = '') {
-		if (text === '' || typeof text !== 'string') {
-			return;
-		}
-		let originalText = button.textContent;
-		if (navigator.clipboard || navigator.clipboard.writeText) {
-			navigator.clipboard.writeText(text).then(() => {
-				button.textContent = 'Copied!';
-				button.style.background = '#00a32a';
-
-				setTimeout(() => {
-					button.textContent = originalText;
-					button.style.background = '';
-				}, 2000);
-			})
-		}
-	}
-
 	async loadStats() {
-		const statsContainer = this.container.querySelector('.referral-stats');
+		const statsContainer = this.container.querySelector('.stats-summary');
 		if (!statsContainer) return;
 
 		try {
-			const response = await fetch(`${jvbSettings.api}/referrals/stats`, {
+			const response = await fetch(`${jvbSettings.api}referrals/my-stats?user=${jvbSettings.currentUser}`, {
 				headers: { 'X-WP-Nonce': jvbSettings.nonce }
 			});
 
@@ -278,80 +350,129 @@
 	}
 
 	/**
+	 * Load recent referrals (last 5)
+	 */
+	async loadRecentReferrals() {
+		const container = this.container.querySelector('.recent-referrals-list');
+		if (!container) return;
+
+		try {
+			const response = await fetch(`${jvbSettings.api}referrals/my-referrals?limit=5&user=${jvbSettings.currentUser}`, {
+				headers: { 'X-WP-Nonce': jvbSettings.nonce }
+			});
+
+			const data = await response.json();
+			if (data.success && data.referrals) {
+				this.renderRecentReferrals(container, data.referrals);
+			} else {
+				container.innerHTML = '<p class="no-referrals">No referrals yet</p>';
+			}
+		} catch (error) {
+			console.error('Error loading referrals:', error);
+			container.innerHTML = '<p class="error">Failed to load referrals</p>';
+		}
+	}
+
+	/**
+	 * Render recent referrals list
+	 */
+	renderRecentReferrals(container, referrals) {
+		if (!referrals || referrals.length === 0) {
+			container.innerHTML = '<p class="no-referrals">Share your code to get started!</p>';
+			return;
+		}
+
+		const html = referrals.map(ref => `
+			<div class="referral-item">
+				<div class="referral-info">
+					<strong>${window.escapeHtml(ref.referee_name)}</strong>
+					<span class="status-badge ${ref.status}">${ref.status}</span>
+				</div>
+				<div class="referral-date">${this.formatDate(ref.referred_at)}</div>
+			</div>
+		`).join('');
+
+		container.innerHTML = html;
+	}
+
+	/**
+	 * 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) {
-		console.log('Form Submission!');
-		window.debouncer.cancel('check-referral');
 		event.preventDefault();
-		console.log('Still working?');
 
 		const form = event.target;
-
-		// Get form data
 		const formData = new FormData(form);
 
-		let data = {};
-
 		// Disable form
 		this.setFormLoading(true, form);
 
 		try {
+			let result = { success: false, message: '' };
 
-			let result = {success: false, message: ''};
-			console.log(form);
-			console.log(form.id);
 			if (form.id === 'referral-code-form') {
-				if (!formData.get('name')) {
-					result.message += 'We need your name to know who you are.';
-				}
-				if (!formData.get('email')) {
-					result.message += 'We need your email to confirm you have access to it.';
-				}
-				if (!formData.get('referral_code')) {
-					result.message += 'We need the referral code to know who sent you.';
-				}
-				if (formData.get('name') && formData.get('email') && formData.get('referral_code')) {
-					data.name = formData.get('name');
-					data.email = formData.get('email');
-					data.code = formData.get('referral_code');
+				const data = {
+					name: formData.get('referral_name'),
+					email: formData.get('referral_email'),
+					code: formData.get('referral_code')
+				};
+
+				if (!data.name || !data.email || !data.code) {
+					result.message = 'Please fill in all fields';
+				} else {
 					result = await this.makeRequest('referrals/register', data);
 				}
-			} else if (form.id === 'login-form' && formData.get('login-email')) {
-				data.type = 'login';
-				data.email = formData.get('login-email');
-				data.context = {};
-				data.context['redirect_to'] = window.location.href+'?seeReferral=1';
-				console.log('Making Request with: ', data);
-				result = await this.makeRequest('magic-link', data);
+			} else if (form.id === 'login-form') {
+				const data = {
+					type: 'login',
+					email: formData.get('login_email'),
+					context: {
+						redirect_to: window.location.href + '?seeReferral=1'
+					}
+				};
+				result = await this.makeRequest('magic', data);
 			}
 
-
 			if (result.success) {
-				this.handleSuccess(result);
+				this.handleSuccess(form, result);
 			} else {
-				this.showMessage(result.message || 'Something went wrong. Please try again.', 'error');
-				this.setFormLoading(false, form);
+				this.showFormMessage(form, result.message || 'Something went wrong. Please try again.', 'error');
 			}
 		} catch (error) {
-			console.error('Error registering:', error);
-			this.showMessage('Something went wrong. Please try again.', 'error');
-			this.setFormLoading(false, form);
+			console.error('Error submitting form:', error);
+			this.showFormMessage(form, 'Something went wrong. Please try again.', 'error');
 		} finally {
 			this.setFormLoading(false, form);
 		}
 	}
 
 	async makeRequest(endpoint, data) {
-		if (![
-			'magic-link',
+		const validEndpoints = [
+			'magic',
 			'referrals/register',
 			'referrals/check-code'
-		].includes(endpoint)) {
-			return {success:false, message: 'Something went wrong (Invalid endpoint).'}
+		];
+
+		if (!validEndpoints.includes(endpoint)) {
+			return { success: false, message: 'Invalid endpoint' };
 		}
-		console.log('Endpoint: ', endpoint);
-		console.log('Data: ', data);
+
 		const response = await fetch(`${jvbSettings.api}${endpoint}`, {
 			method: 'POST',
 			headers: {
@@ -367,23 +488,21 @@
 	/**
 	 * Show success state
 	 */
-	handleSuccess(result) {
-		//Hide forms
-		this.container.querySelectorAll('form').forEach(form => {
-			window.fade(form, false);
-		});
-
-		const successState = this.container.querySelector('.success-message');
-		if (!successState) return;
+	handleSuccess(form, result) {
+		// Hide form
+		form.style.display = 'none';
 
 		// Show success message
-		successState.hidden = false;
+		const successDiv = form.nextElementSibling;
+		if (successDiv && successDiv.classList.contains('success-content')) {
+			successDiv.hidden = false;
 
-		// Scroll to message
-		successState.scrollIntoView({
-			behavior: 'smooth',
-			block: 'center'
-		});
+			// Scroll to message
+			successDiv.scrollIntoView({
+				behavior: 'smooth',
+				block: 'center'
+			});
+		}
 
 		// Fire custom event
 		this.dispatchEvent('emailSent', {
@@ -392,38 +511,45 @@
 	}
 
 	/**
-	 * Set form loading state
+	 * Show message in form status area
 	 */
-	setFormLoading(loading, form) {
-		const inputs = form.querySelectorAll('input');
+	showFormMessage(form, text, type = 'error') {
+		const status = form.querySelector('.status');
+		if (!status) return;
 
-		inputs.forEach(input => input.disabled = loading);
-		let status = form.querySelector('.status');
-		let message = status.querySelector('.message');
-		status.hidden = loading;
-		status.classList.toggle('loading', loading);
-		if (loading) {
-			message.textContent = 'Checking with server...';
-		} else {
-			message.textContent = '';
+		const message = status.querySelector('.message');
+		if (message) {
+			message.textContent = text;
+		}
+
+		status.hidden = false;
+		status.className = `status ${type}`;
+
+		if (type === 'error') {
+			setTimeout(() => {
+				status.hidden = true;
+			}, 5000);
 		}
 	}
 
 	/**
-	 * Show message
+	 * Set form loading state
 	 */
-	showMessage(text, type = 'success') {
-		const messageDiv = this.container.querySelector('#referral-message');
-		if (!messageDiv) return;
+	setFormLoading(loading, form) {
+		const inputs = form.querySelectorAll('input, button');
+		inputs.forEach(input => input.disabled = loading);
 
-		messageDiv.textContent = text;
-		messageDiv.className = 'message ' + type;
-		messageDiv.style.display = 'block';
+		const status = form.querySelector('.status');
+		if (status) {
+			status.classList.toggle('loading', loading);
 
-		if (type === 'error') {
-			setTimeout(() => {
-				messageDiv.style.display = 'none';
-			}, 5000);
+			if (loading) {
+				status.hidden = false;
+				const message = status.querySelector('.message');
+				if (message) {
+					message.textContent = 'Sending...';
+				}
+			}
 		}
 	}
 
@@ -439,8 +565,6 @@
 	}
 }
 
-
 document.addEventListener('DOMContentLoaded', () => {
 	window.jvbReferral = new Referral();
 });
-

--
Gitblit v1.10.0