/** * Referral Widget Manager * Handles both logged-in share widget and public code validation widget * */ class Referral { constructor() { this.container = document.querySelector('.jvb-referral'); if (!this.container) { return; } this.a11y = window.jvbA11y; this.toggle = document.querySelector('button[data-action="toggle-referral"]'); this.initElements(); this.initListeners(); this.checkForReferral(); } initElements() { this.selectors = { copy: 'button.copy', login: '.login', 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); }, onClose: () => { this.forms.forEach(form => { form.removeEventListener('submit', this.submitHandler); }); this.container.removeEventListener('click', this.clickHandler); this.container.removeEventListener('input', this.inputHandler); } }); this.tabs = null; if (this.container.querySelector('nav.tabs')) { this.tabs = new window.jvbTabs(this.container, {updateURL: false}); } this.ui = window.uiFromSelectors(this.selectors, this.container); } initListeners() { this.clickHandler = this.handleClick.bind(this); this.inputHandler = this.handleInput.bind(this); this.submitHandler = this.handleFormSubmit.bind(this); this.changeHandler = this.handleChange.bind(this); } 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); } } } handleChange(e) { if (e.target.id === 'referral-code') { window.debouncer.schedule( 'check-referral', ()=> this.makeRequest('referrals/check-code', {code: e.target.value})), 150 } } handleInput(e) { if (e.target.id === 'referral-code') { e.target.value = e.target.value.toUpperCase(); } } // ========================================== // SHARE WIDGET (Logged-In Users) // ========================================== initShareWidget() { this.initCopyButton(); this.loadStats(); } /** * Check for ?ref parameter in URL and pre-fill code */ 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'); if (!codeInput) return; // Convert to uppercase const code = refCode.toUpperCase(); // Pre-fill the code input codeInput.value = code; codeInput.readOnly = true; // Make it read-only since it came from link this.popup.togglePopup(); // Validate the code immediately to show referrer info try { const referrer = await this.validateCodeOnly(code); if (referrer.success) { // Show referrer info banner this.showReferrerBanner(referrer.referrer_name, code); // Focus on name input (first empty field) const nameInput = this.container.querySelector('#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'); } } catch (error) { console.error('Error validating code:', error); codeInput.readOnly = false; } // Clean up URL (remove ?ref parameter) 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); window.history.replaceState({}, document.title, url.toString()); } /** * Validate code without registering (just check if valid) */ async validateCodeOnly(code) { const response = await fetch(`${jvbSettings.api}/referrals/check-code`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code: code }) }); return await response.json(); } /** * Show banner with referrer info */ 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 = ` `; // 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'); if (!statsContainer) return; try { const response = await fetch(`${jvbSettings.api}/referrals/stats`, { headers: { 'X-WP-Nonce': jvbSettings.nonce } }); const data = await response.json(); if (data.success && data.stats) { this.updateStats(data.stats); } } catch (error) { console.error('Error loading 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); } } /** * 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: ''}; 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'); 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', data); } if (result.success) { this.handleSuccess(result); } else { this.showMessage(result.message || 'Something went wrong. Please try again.', 'error'); this.setFormLoading(false, form); } } catch (error) { console.error('Error registering:', error); this.showMessage('Something went wrong. Please try again.', 'error'); this.setFormLoading(false, form); } finally { this.setFormLoading(false, form); } } async makeRequest(endpoint, data) { if (![ 'magic', 'referrals/register', 'referrals/check-code' ].includes(endpoint)) { return {success:false, message: 'Something went wrong (Invalid endpoint).'} } console.log('Endpoint: ', endpoint); console.log('Data: ', data); const response = await fetch(`${jvbSettings.api}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': jvbSettings.nonce, }, body: JSON.stringify(data) }); return await response.json(); } /** * 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; // Show success message successState.hidden = false; // Scroll to message successState.scrollIntoView({ behavior: 'smooth', block: 'center' }); // Fire custom event this.dispatchEvent('emailSent', { email: result.email }); } /** * Set form loading state */ setFormLoading(loading, form) { const inputs = form.querySelectorAll('input'); 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 = ''; } } /** * Show message */ showMessage(text, type = 'success') { const messageDiv = this.container.querySelector('#referral-message'); if (!messageDiv) return; messageDiv.textContent = text; messageDiv.className = 'message ' + type; messageDiv.style.display = 'block'; if (type === 'error') { setTimeout(() => { messageDiv.style.display = 'none'; }, 5000); } } /** * Dispatch custom event */ dispatchEvent(eventName, detail) { const event = new CustomEvent('referralWidget:' + eventName, { detail: detail, bubbles: true }); this.container.dispatchEvent(event); } } document.addEventListener('DOMContentLoaded', () => { window.jvbReferral = new Referral(); });