UNPKG

ultimate-jekyll-manager

Version:
504 lines (420 loc) 14.4 kB
/** * Admin Notifications Page JavaScript */ // Libraries import { FormManager } from '__main_assets__/js/libs/form-manager.js'; import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js'; import { getPrerenderedIcon } from '__main_assets__/js/libs/prerendered-icons.js'; let webManager = null; // Module export default (Manager) => { return new Promise(async function (resolve) { // Shortcuts webManager = Manager.webManager; // Initialize when DOM is ready await webManager.dom().ready(); try { await initializeNotificationCreator(); } catch (error) { webManager.sentry().captureException(new Error('Failed to initialize notification creator', { cause: error })); } // Resolve after initialization return resolve(); }); }; // Global state let formManager = null; let stats = { totalUsers: 0, filteredUsers: 0, }; let autoSubmitTimer = null; let isInitialized = false; // Main initialization async function initializeNotificationCreator() { // Prevent re-initialization if (isInitialized) { return; } isInitialized = true; // Initialize FormManager initializeFormManager(); // Initialize UI elements initializeUI(); // Show loading spinner on user counts initially showUserCountLoading(); // Setup auth listener webManager.auth().listen({ account: false }, async (state) => { console.log('Auth state for notification creator:', state); // Show loading spinner while fetching showUserCountLoading(); // Fetch initial user stats await fetchUserStats(); // Update UI with stats updateUserCount(); }); } // Initialize FormManager function initializeFormManager() { formManager = new FormManager('#notification-form', { allowResubmit: false, }); formManager.on('change', ({ data }) => { // Update preview in real-time updatePreview(data); // Update character counts updateCharacterCounts(data); // Check auto-submit status checkAutoSubmit(data); }); formManager.on('submit', async ({ data }) => { // Transform data for API const payload = transformDataForAPI(data); // Add user stats payload.reach = { available: { total: stats.totalUsers }, filtered: { total: stats.filteredUsers }, }; // Call API await sendNotification(payload); // Track success trackNotificationSent(payload); // Success formManager.showSuccess(`Notification sent successfully to ${stats.filteredUsers.toLocaleString()} users!`); }); } // Initialize UI elements function initializeUI() { // Icon preview updater const $iconInput = document.querySelector('input[name="notification.icon"]'); if ($iconInput) { $iconInput.addEventListener('input', (e) => { updateIconPreview(e.target.value); }); } // Title character counter const $titleInput = document.querySelector('input[name="notification.title"]'); if ($titleInput) { // Set default value in dev mode if (webManager.isDevelopment()) { $titleInput.value = 'Test Notification Title'; } $titleInput.addEventListener('input', (e) => { const length = e.target.value.length; const $titleCount = document.querySelector('#title-char-count'); if ($titleCount) { $titleCount.textContent = `${length} / 60`; $titleCount.className = length > 60 ? 'text-danger' : 'text-muted'; } }); } // Body character counter const $bodyInput = document.querySelector('textarea[name="notification.body"]'); if ($bodyInput) { // Set default value in dev mode if (webManager.isDevelopment()) { $bodyInput.value = 'This is a test notification body message for development.'; } $bodyInput.addEventListener('input', (e) => { const length = e.target.value.length; const $bodyCount = document.querySelector('#body-char-count'); if ($bodyCount) { $bodyCount.textContent = `${length} / 200`; $bodyCount.className = length > 200 ? 'text-danger' : 'text-muted'; } }); } // Auto-submit button const $autoSubmitBtn = document.querySelector('.btn-set-auto-submit'); if ($autoSubmitBtn) { $autoSubmitBtn.addEventListener('click', () => { setAutoSubmitTime(); }); } // Clear auto-submit button const $clearAutoSubmitBtn = document.querySelector('.btn-clear-auto-submit'); if ($clearAutoSubmitBtn) { $clearAutoSubmitBtn.addEventListener('click', () => { clearAutoSubmit(); }); } // Notification preview click handler const $notificationPreview = document.querySelector('#notification-preview-clickable'); if ($notificationPreview) { $notificationPreview.addEventListener('click', () => { const $clickActionInput = document.querySelector('input[name="notification.clickAction"]'); if ($clickActionInput && $clickActionInput.value) { // Open the URL in a new tab window.open($clickActionInput.value, '_blank', 'noopener,noreferrer'); } }); } // Initialize preview with default values and trigger form change to update everything updatePreview(); // Trigger initial form change to update character counts and preview with form data if (formManager) { setTimeout(() => { const data = formManager.getData(); updatePreview(data); updateCharacterCounts(data); checkAutoSubmit(data); }, 100); } } // Transform data for API function transformDataForAPI(formData) { console.log('[Debug] transformDataForAPI called'); console.log('[Debug] formData received:', formData); console.log('[Debug] formData.notification:', formData.notification); const now = new Date(); const notification = formData.notification || {}; console.log('[Debug] notification object:', notification); console.log('[Debug] notification.clickAction:', notification.clickAction); console.log('[Debug] typeof notification:', typeof notification); console.log('[Debug] Object.keys(notification):', Object.keys(notification)); // Generate ID const id = now.getTime(); // Build click action URL with tracking const redirectUrl = new URL('https://promo-server.itwcreativeworks.com/redirect/notification'); redirectUrl.searchParams.set('id', id); redirectUrl.searchParams.set('type', 'notification'); // Make sure we have the actual clickAction value const clickActionValue = notification.clickAction || ''; console.log('[Debug] clickActionValue to be set in URL:', clickActionValue); redirectUrl.searchParams.set('url', clickActionValue); return { id: id, notification: { icon: notification.icon || '', title: notification.title || '', body: notification.body || '', clickAction: redirectUrl.toString(), }, created: now.toISOString(), channels: formData.channels || {}, audience: formData.audience || {}, schedule: formData.schedule || {}, }; } // Send notification via API async function sendNotification(payload) { const functionsUrl = getAPIFunctionsUrl(); return authorizedFetch(functionsUrl, { method: 'POST', timeout: 60000, response: 'json', tries: 1, log: true, body: { command: 'admin:send-notification', payload: payload, }, }); } // Fetch user statistics async function fetchUserStats() { try { const response = await authorizedFetch(getAPIFunctionsUrl(), { method: 'POST', timeout: 60000, response: 'json', tries: 2, log: true, body: { command: 'admin:firestore-read', payload: { path: 'meta/stats', }, }, }); // Extract user count from response const total = response?.notifications?.total || 0; stats.totalUsers = total; stats.filteredUsers = total; // Initially all users } catch (error) { console.error('Failed to fetch user stats:', error); webManager.sentry().captureException(new Error('Failed to fetch user stats', { cause: error })); // Use default values stats.totalUsers = 0; stats.filteredUsers = 0; } } // Update user count display function updateUserCount() { const $countElements = document.querySelectorAll('.notification-user-count'); $countElements.forEach(el => { // Remove loading spinner and show the count el.innerHTML = `${stats.filteredUsers.toLocaleString()} / ${stats.totalUsers.toLocaleString()}`; }); } // Show loading spinner on user count elements function showUserCountLoading() { const $countElements = document.querySelectorAll('.notification-user-count'); $countElements.forEach(el => { // Add spinner with loading text el.innerHTML = '<span class="spinner-border spinner-border-sm me-1" role="status"></span>Loading...'; }); } // Update preview function updatePreview(formData = null) { if (!formData) { formData = formManager?.getData() || {}; } const notification = formData.notification || {}; // Update preview elements const $previewIcon = document.querySelector('#preview-icon'); const $previewTitle = document.querySelector('#preview-title'); const $previewBody = document.querySelector('#preview-body'); const $previewTime = document.querySelector('#preview-time'); if ($previewIcon && notification.icon) { $previewIcon.src = notification.icon; $previewIcon.onerror = () => { $previewIcon.src = 'https://via.placeholder.com/50'; }; } if ($previewTitle) { $previewTitle.textContent = notification.title || 'Notification Title'; } if ($previewBody) { $previewBody.textContent = notification.body || 'This is what your notification body will look like...'; } if ($previewTime) { $previewTime.textContent = 'Now'; } } // Update icon preview function updateIconPreview(url) { const $previewIcon = document.querySelector('#preview-icon'); if ($previewIcon) { if (url && url.match(/^https?:\/\/.+/)) { $previewIcon.src = url; $previewIcon.onerror = () => { $previewIcon.src = 'https://via.placeholder.com/50'; }; } else { $previewIcon.src = 'https://via.placeholder.com/50'; } } } // Update character counts function updateCharacterCounts(formData) { const notification = formData.notification || {}; // Update title count const $titleCount = document.querySelector('#title-char-count'); if ($titleCount) { const length = (notification.title || '').length; $titleCount.textContent = `${length} / 60`; $titleCount.className = length > 60 ? 'text-danger' : 'text-muted'; } // Update body count const $bodyCount = document.querySelector('#body-char-count'); if ($bodyCount) { const length = (notification.body || '').length; $bodyCount.textContent = `${length} / 200`; $bodyCount.className = length > 200 ? 'text-danger' : 'text-muted'; } } // Set auto-submit time function setAutoSubmitTime() { // Set to tomorrow at 10 AM const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(10, 0, 0, 0); // Format for input fields const dateStr = tomorrow.toISOString().split('T')[0]; const timeStr = '10:00'; // Set values const $dateInput = document.querySelector('input[name="schedule.date"]'); const $timeInput = document.querySelector('input[name="schedule.time"]'); if ($dateInput) $dateInput.value = dateStr; if ($timeInput) $timeInput.value = timeStr; // Start countdown startAutoSubmitCountdown(tomorrow); } // Clear auto-submit function clearAutoSubmit() { // Clear inputs const $dateInput = document.querySelector('input[name="schedule.date"]'); const $timeInput = document.querySelector('input[name="schedule.time"]'); if ($dateInput) $dateInput.value = ''; if ($timeInput) $timeInput.value = ''; // Clear countdown clearAutoSubmitCountdown(); } // Check auto-submit status function checkAutoSubmit(formData) { if (formData.schedule?.date && formData.schedule?.time) { const scheduledDate = new Date(`${formData.schedule.date}T${formData.schedule.time}`); if (scheduledDate > new Date()) { startAutoSubmitCountdown(scheduledDate); } else { clearAutoSubmitCountdown(); } } else { clearAutoSubmitCountdown(); } } // Start auto-submit countdown function startAutoSubmitCountdown(targetDate) { clearAutoSubmitCountdown(); const $countdownEl = document.querySelector('#auto-submit-countdown'); if (!$countdownEl) { return; } $countdownEl.classList.remove('d-none'); autoSubmitTimer = setInterval(() => { const now = new Date(); const diff = targetDate - now; if (diff <= 0) { // Time to submit! clearAutoSubmitCountdown(); // Fetch updated stats before submitting fetchUserStats() .then(() => updateUserCount()) .finally(() => { // Programmatically submit the form formManager.$form.requestSubmit(); }); return; } const hours = Math.floor(diff / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((diff % (1000 * 60)) / 1000); $countdownEl.innerHTML = `<i class="bi bi-clock"></i> Auto-submit in: ${hours}h ${minutes}m ${seconds}s`; }, 1000); } // Clear auto-submit countdown function clearAutoSubmitCountdown() { if (autoSubmitTimer) { clearInterval(autoSubmitTimer); autoSubmitTimer = null; } const $countdownEl = document.querySelector('#auto-submit-countdown'); if ($countdownEl) { $countdownEl.classList.add('d-none'); $countdownEl.innerHTML = ''; } } // Get API functions URL function getAPIFunctionsUrl() { // Get functions URL from webManager and add the endpoint //@TODO remove this when fixed api url return `${webManager.getFunctionsUrl('prod')}/bm_api`; } // Tracking functions function trackNotificationSent(payload) { gtag('event', 'notification_sent', { notification_type: 'admin', user_count: stats.filteredUsers, channels: Object.keys(payload.channels || {}).filter(c => payload.channels[c]?.enabled).join(','), }); fbq('trackCustom', 'AdminNotificationSent', { users: stats.filteredUsers, channels: Object.keys(payload.channels || {}).filter(c => payload.channels[c]?.enabled), }); ttq.track('SubmitForm', { content_id: 'admin-notification', content_type: 'product', content_name: 'Admin Notification', }); }