class ReferralAdmin { constructor() { this.a11y = window.jvbA11y; this.referral = window.jvbReferral; this.hasCopy = navigator.clipboard && navigator.clipboard.writeText; this.initElements(); this.initListeners(); } initElements() { this.selectors = { copyBtn: '.copy-btn', invite: 'form.invite', adminList: '.items-list.referral', dash: '.replace .referral-dashboard', list: '.referrals-list' } this.ui = window.uiFromSelectors(this.selectors); this.tabs = null; if (this.ui.dash) { this.tabs = new window.jvbTabs(this.ui.dash); this.initViewController(); } 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', } ); } }); } } initListeners() { this.clickHandler = this.handleClick.bind(this); document.addEventListener('click', this.clickHandler); if (window.jvbQueue) { window.jvbQueue.subscribe(this.handleQueueEvent.bind(this)); } } handleClick(e) { const target = e.target.closest('.copy-btn'); if (target) { this.handleCopyClick(target); } } handleCopyClick(button) { const targetId = button.dataset.target; const codeElement = button.closest('.row').querySelector(`#${targetId}`); if (!codeElement) return; const text = codeElement.textContent.trim(); // Try clipboard API first if (this.hasCopy) { navigator.clipboard.writeText(text).then(() => { button.classList.toggle('success'); setTimeout(() => { button.classList.remove('success'); }, 1500); }); } } initViewController() { if (!this.referral.listStore || !this.ui.adminList) return; this.view = new window.jvbViews(this.ui.adminList, this.referral.listStore); this.view.subscribe((event, data) => { switch(event) { case 'item-action': this.handleItemAction(data); break; case 'bulk-action': this.handleBulkAction(data); break; } }); } /** * 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.referral.listStore) this.referral.listStore.fetch(); if (this.referral.statsStore) this.referral.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); } } /*************************** BACKEND stuff ***************************/ handleQueueEvent(event, data) { if (event !== 'operation-complete') return; if (!data.details) return; // Not our operation // Check if it's our invite operation if (data.details.successful || data.details.failed) { this.showInviteResults(data.details); } } // showInviteResults(details) { // const successful = details.successful?.length || 0; // const failed = details.failed?.length || 0; // // if (failed === 0) { // this.a11y?.announce(`All ${successful} invitations sent successfully!`); // // Clear the form // this.ui.invite?.reset(); // } else { // // Show which ones failed // const failureList = details.failed // .map(f => `• ${f.name} (${f.email}): ${f.reason}`) // .join('\n'); // // const message = `${successful} sent, ${failed} failed:\n${failureList}`; // // // Show in a modal or persistent notification // alert(message); // Or use a nicer notification system // } // } showInviteResults(details) { if (!this.ui.invite) return; const tagListField = this.ui.invite.querySelector('[data-field="invite"]'); if (!tagListField) return; const tagList = tagListField.querySelector('.tag-list'); if (!tagList) return; // Map results by email for easy lookup const resultMap = new Map(); details.successful?.forEach(item => { resultMap.set(item.email, { status: 'success', name: item.name }); }); details.failed?.forEach(item => { resultMap.set(item.email, { status: 'error', name: item.name, reason: item.reason }); }); // Update each tag with status const tags = tagList.querySelectorAll('.tag'); tags.forEach(tag => { const tagData = JSON.parse(tag.dataset.value || '{}'); const result = resultMap.get(tagData.email); if (result) { this.updateTagStatus(tag, result); } }); // Show summary notification this.showInviteSummary(details); } updateTagStatus(tag, result) { // Remove existing status tag.classList.remove('success', 'error'); const existingIcon = tag.querySelector('.status-icon'); if (existingIcon) existingIcon.remove(); // Add new status tag.classList.add(result.status); const icon = document.createElement('span'); icon.className = 'status-icon'; icon.innerHTML = result.status === 'success' ? window.jvbIcon('check-circle', { size: 14 }) : window.jvbIcon('warning-circle', { size: 14 }); if (result.reason) { icon.title = result.reason; } // Insert icon before the remove button const removeBtn = tag.querySelector('.remove-tag'); if (removeBtn) { tag.insertBefore(icon, removeBtn); } else { tag.appendChild(icon); } } showInviteSummary(details) { const successful = details.successful?.length || 0; const failed = details.failed?.length || 0; let message = `Invites sent! ${successful} successful`; if (failed > 0) { message += `, ${failed} failed`; } // Show in form status or as toast notification if (this.formController) { this.formController.showStatus(this.ui.invite, 'submitted', message); } // Optionally show detailed failures if (failed > 0) { this.showFailureDetails(details.failed); } } showFailureDetails(failed) { // Create a details element or modal showing why each failed const detailsHTML = `
${failed.length} invitation(s) failed - click for details
`; // Insert after the form or in a notification area const statusArea = this.ui.invite.querySelector('.fstatus'); if (statusArea) { const existingDetails = statusArea.querySelector('.invite-failures'); if (existingDetails) existingDetails.remove(); statusArea.insertAdjacentHTML('beforeend', detailsHTML); } } } document.addEventListener('DOMContentLoaded', async function () { window.auth.subscribe((event) => { if (event === 'auth-loaded') { window.jvbAdminReferral = new ReferralAdmin(); } }); });