| | |
| | | |
| | | class Referral { |
| | | constructor() { |
| | | this.container = document.querySelector('.jvb-referral'); |
| | | this.container = document.querySelector('aside.referral'); |
| | | if (!this.container) { |
| | | return; |
| | | } |
| | |
| | | this.a11y = window.jvbA11y; |
| | | this.toggle = document.querySelector('button[data-action="toggle-referral"]'); |
| | | |
| | | this.hasCopy = navigator.clipboard && navigator.clipboard.writeText; |
| | | this.initElements(); |
| | | this.initStore(); |
| | | this.initListeners(); |
| | | this.checkForReferral(); |
| | | |
| | | // Load additional data for logged-in users |
| | | if (this.isLoggedIn()) { |
| | | this.loadStats(); |
| | | this.loadRecentReferrals(); |
| | | } |
| | | } |
| | | |
| | | initElements() { |
| | |
| | | copyBtn: '.copy-btn', |
| | | checkCode: '.check-code-btn', |
| | | submit: '[type=submit]', |
| | | recentList: '.recent-referrals-list', |
| | | stats: { |
| | | codeUsed: '[data-stat="code_used"]', |
| | | consultations: '[data-stat="consultations"]', |
| | | treatments: '[data-stat="treatments"]', |
| | | rewards: '[data-stat="total_rewards"]' |
| | | }, |
| | | }; |
| | | |
| | | this.forms = this.container.querySelectorAll('form'); |
| | |
| | | }); |
| | | |
| | | 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); |
| | | |
| | | this.ui = window.uiFromSelectors(this.selectors); |
| | | |
| | | |
| | | |
| | | if (!this.hasCopy) { |
| | | document.querySelectorAll(this.selectors.copyBtn).forEach(btn => { |
| | | btn.remove(); |
| | | }); |
| | | } |
| | | this.formController = null; |
| | | } |
| | | |
| | | initStore() { |
| | | if (!this.isLoggedIn()) return; |
| | | |
| | | const stores = window.jvbStore.register( |
| | | 'referrals', |
| | | [ |
| | | // Dashboard stats store |
| | | { |
| | | storeName: 'stats', |
| | | keyPath: 'user_id', |
| | | endpoint: 'referrals/stats', |
| | | TTL: 5 * 60 * 1000, |
| | | showLoading: false, |
| | | delayFetch: false, |
| | | filters: { |
| | | type: 'dashboard', |
| | | user: window.auth.getUser() |
| | | } |
| | | }, |
| | | // Referrals list store |
| | | { |
| | | storeName: 'list', |
| | | keyPath: 'id', |
| | | endpoint: 'referrals', |
| | | TTL: 10 * 60 * 1000, |
| | | showLoading: false, |
| | | delayFetch: false, |
| | | filters: { |
| | | user: window.auth.getUser(), |
| | | status: 'all', |
| | | limit: 50, |
| | | offset: 0 |
| | | } |
| | | } |
| | | ] |
| | | ); |
| | | |
| | | this.statsStore = stores.stats; |
| | | this.listStore = stores.list; |
| | | |
| | | // Subscribe to store events |
| | | if (this.statsStore) { |
| | | this.statsStore.subscribe(this.handleStatsEvent.bind(this)); |
| | | } |
| | | if (this.listStore) { |
| | | this.listStore.subscribe(this.handleListEvent.bind(this)); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | initListeners() { |
| | | this.clickHandler = this.handleClick.bind(this); |
| | | this.inputHandler = this.handleInput.bind(this); |
| | |
| | | } |
| | | |
| | | isLoggedIn() { |
| | | return Boolean(jvbSettings.currentUser); |
| | | return Boolean(window.auth.getUser()); |
| | | } |
| | | |
| | | /** |
| | | * Handle DataStore stats events |
| | | */ |
| | | handleStatsEvent(event, data) { |
| | | switch(event) { |
| | | case 'data-loaded': |
| | | if (data.items && data.items.length > 0) { |
| | | this.updateStatsDisplay(); |
| | | } |
| | | break; |
| | | case 'fetch-error': |
| | | console.error('Error loading stats:', data.error); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Handle DataStore list events |
| | | */ |
| | | handleListEvent(event, data) { |
| | | switch(event) { |
| | | case 'data-loaded': |
| | | // Let ViewController handle main list rendering |
| | | // Only update sidebar preview if it exists |
| | | if (this.ui.recentList) { |
| | | this.renderRecentReferrals(); |
| | | } |
| | | break; |
| | | case 'fetch-error': |
| | | console.error('Error loading referrals:', data.error); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Update stats display |
| | | */ |
| | | updateStatsDisplay() { |
| | | if (!this.statsStore.data.size === 0) return; |
| | | let stats = this.statsStore.data.get(parseInt(window.auth.getUser())); |
| | | const updates = { |
| | | total: stats['code_used'] || 0, |
| | | treated: stats.treatments || 0, |
| | | pending: stats.pending || 0, |
| | | rewards: '$' + parseFloat(stats['total_rewards'] || 0).toFixed(2) |
| | | }; |
| | | |
| | | Object.entries(updates).forEach(([key, value]) => { |
| | | const element = this.container.querySelector(`[data-stat="${key}"]`); |
| | | if (element) { |
| | | element.textContent = value; |
| | | } |
| | | }); |
| | | |
| | | // Also update stat cards if on dashboard |
| | | const statCards = this.container.querySelectorAll('.stats .card'); |
| | | if (statCards.length >= 4) { |
| | | statCards[0].querySelector('.stat-number').textContent = updates.code_used; |
| | | statCards[1].querySelector('.stat-number').textContent = updates.consultations; |
| | | statCards[2].querySelector('.stat-number').textContent = updates.treatments; |
| | | statCards[3].querySelector('.stat-number').textContent = updates.total_rewards; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | handleClick(e) { |
| | | const target = e.target.closest('.copy-btn, .check-code-btn, .attn'); |
| | | if (!target) return; |
| | |
| | | const text = codeElement.textContent.trim(); |
| | | |
| | | // Try clipboard API first |
| | | if (navigator.clipboard && navigator.clipboard.writeText) { |
| | | 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); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Handle error response with field-specific feedback |
| | | */ |
| | | handleError(form, result) { |
| | | const { message, code, field } = result; |
| | | |
| | | // If there's a specific field, highlight it |
| | | if (field) { |
| | | this.showFieldError(form, field, message); |
| | | } else { |
| | | // Fallback to selection |
| | | this.selectText(codeElement); |
| | | this.showCopyFallback(button); |
| | | // 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; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Select text in element |
| | | * Show error for specific field |
| | | */ |
| | | 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(); |
| | | 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}"]`); |
| | | } |
| | | |
| | | 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 copy success feedback |
| | | * Clear all form errors |
| | | */ |
| | | showCopySuccess(button) { |
| | | const originalHTML = button.innerHTML; |
| | | button.innerHTML = window.jvbIcon('check', {size: 16}) + ' Copied!'; |
| | | button.classList.add('success'); |
| | | 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('success'); |
| | | }, 2000); |
| | | // Hide form status |
| | | const statusWrap = form.querySelector('.fstatus'); |
| | | if (statusWrap) { |
| | | statusWrap.hidden = true; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Show fallback message |
| | | */ |
| | | showCopyFallback(button) { |
| | | const originalHTML = button.innerHTML; |
| | | button.innerHTML = '✓ Selected - Press Ctrl+C'; |
| | | button.classList.add('selected'); |
| | | clearFieldValidation(fieldWrapper) { |
| | | if (!fieldWrapper) return; |
| | | |
| | | setTimeout(() => { |
| | | button.innerHTML = originalHTML; |
| | | button.classList.remove('selected'); |
| | | }, 3000); |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | * Check for ?ref parameter in URL and pre-fill code |
| | | */ |
| | | async checkForReferral() { |
| | | const isLoggedIn = this.getUrlParameter('seeReferral'); |
| | | const refCode = this.getUrlParameter('ref'); |
| | | const refName = this.getUrlParameter('rname'); |
| | | const refEmail = this.getUrlParameter('remail'); |
| | | const seeReferral = this.getUrlParameter('seeReferral'); |
| | | |
| | | if (!isLoggedIn && !refCode) { |
| | | if (!refCode && !seeReferral) { |
| | | return; |
| | | } |
| | | |
| | | if (!refCode) { |
| | | // If logged in user just wants to see referral popup |
| | | if (seeReferral && !refCode) { |
| | | this.popup.openPopup(); |
| | | this.removeUrlParameter('seeReferral'); |
| | | return; |
| | | } |
| | | |
| | |
| | | codeInput.value = code; |
| | | codeInput.readOnly = true; |
| | | |
| | | this.popup.togglePopup(); |
| | | // If we have token data, prefill name and email too |
| | | if (refName || refEmail) { |
| | | const nameInput = this.container.querySelector('[name="referral_name"]'); |
| | | if (nameInput) { |
| | | nameInput.value = refName; |
| | | } |
| | | |
| | | const emailInput = this.container.querySelector('[name="referral_email"]'); |
| | | if (emailInput) { |
| | | emailInput.value = refEmail; |
| | | } |
| | | } |
| | | |
| | | // Open the sidebar popup |
| | | this.popup.openPopup(); |
| | | |
| | | // Validate the code immediately |
| | | try { |
| | |
| | | ); |
| | | } |
| | | |
| | | // Focus on name input |
| | | // Focus on name input if not prefilled |
| | | const nameInput = this.container.querySelector('[name="referral_name"]'); |
| | | if (nameInput) { |
| | | if (nameInput && !nameInput.value) { |
| | | nameInput.focus(); |
| | | } |
| | | } else { |
| | |
| | | |
| | | // Clean up URL |
| | | this.removeUrlParameter('ref'); |
| | | this.removeUrlParameter('rname'); |
| | | this.removeUrlParameter('remail'); |
| | | } |
| | | |
| | | getUrlParameter(name) { |
| | |
| | | * Validate code without registering |
| | | */ |
| | | async validateCodeOnly(code) { |
| | | const response = await fetch(`${jvbSettings.api}referrals/check-code`, { |
| | | const response = await fetch(`${jvbSettings.api}referrals/code`, { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | 'X-WP-Nonce': jvbSettings.nonce |
| | | 'X-WP-Nonce': window.auth.getNonce() |
| | | }, |
| | | body: JSON.stringify({ code: code }) |
| | | }); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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=${jvbSettings.currentUser}`, { |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 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) { |
| | | renderRecentReferrals() { |
| | | let container = this.ui.recentList; |
| | | let referrals = Array.from(this.listStore.data.values()); |
| | | if (!referrals || referrals.length === 0) { |
| | | container.innerHTML = '<p class="no-referrals">Share your code to get started!</p>'; |
| | | return; |
| | | } |
| | | |
| | | const html = referrals.map(ref => ` |
| | | container.innerHTML = 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> |
| | | <span class="status-badge">${ref.referral_status}</span> |
| | | </div> |
| | | <div class="referral-date">${this.formatDate(ref.referred_at)}</div> |
| | | <div class="referral-date">${window.formatTimeAgo(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' }); |
| | | } |
| | | |
| | | /** |
| | |
| | | const form = event.target; |
| | | const formData = new FormData(form); |
| | | |
| | | // Disable form |
| | | // Clear any existing errors |
| | | this.clearFormErrors(form); |
| | | this.setFormLoading(true, form); |
| | | |
| | | try { |
| | | let result = { success: false, message: '' }; |
| | | |
| | | if (form.id === 'referral-code-form') { |
| | | const data = { |
| | | // Registration with referral code - goes to LoginRoutes |
| | | let data = { |
| | | name: formData.get('referral_name'), |
| | | email: formData.get('referral_email'), |
| | | code: formData.get('referral_code') |
| | | referral_code: formData.get('referral_code') |
| | | }; |
| | | |
| | | if (!data.name || !data.email || !data.code) { |
| | | if (formData.get('cf-turnstile-response')) { |
| | | data['cf-turnstile-response'] = formData.get('cf-turnstile-response'); |
| | | } |
| | | |
| | | if (!data.name || !data.email || !data.referral_code) { |
| | | result.message = 'Please fill in all fields'; |
| | | } else { |
| | | result = await this.makeRequest('referrals/register', data); |
| | | 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'), |
| | | context: { |
| | | redirect_to: window.location.href + '?seeReferral=1' |
| | | } |
| | | }; |
| | | result = await this.makeRequest('magic', data); |
| | | if (formData.get('cf-turnstile-response')) { |
| | | data['cf-turnstile-response'] = formData.get('cf-turnstile-response'); |
| | | } |
| | | if (!data.email) { |
| | | result.message = 'Please fill in your email'; |
| | | } else { |
| | | result = await this.makeRequest('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); |
| | |
| | | async makeRequest(endpoint, data) { |
| | | const validEndpoints = [ |
| | | 'magic', |
| | | 'referrals/register', |
| | | 'referrals/check-code' |
| | | 'auth/register' |
| | | ]; |
| | | |
| | | if (!validEndpoints.includes(endpoint)) { |
| | |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | 'X-WP-Nonce': jvbSettings.nonce, |
| | | '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); |
| | | try { |
| | | return JSON.parse(errorText); |
| | | } catch { |
| | | return { success: false, message: 'Server error' }; |
| | | } |
| | | } |
| | | |
| | | return await response.json(); |
| | | } |
| | | |
| | |
| | | * 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'); |
| | | } |
| | | } |
| | | |
| | |
| | | }); |
| | | this.container.dispatchEvent(event); |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | document.addEventListener('DOMContentLoaded', () => { |
| | | window.jvbReferral = new Referral(); |
| | | document.addEventListener('DOMContentLoaded', async function () { |
| | | window.auth.subscribe((event) => { |
| | | if (event === 'auth-loaded') { |
| | | window.jvbReferral = new Referral(); |
| | | } |
| | | }); |
| | | }); |