diff --git a/static/admin/js/background-tasks.js b/static/admin/js/background-tasks.js index bc7fd04a..4834b449 100644 --- a/static/admin/js/background-tasks.js +++ b/static/admin/js/background-tasks.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch /** * Background Tasks Monitoring Component * Manages the background tasks monitoring page @@ -37,18 +38,26 @@ function backgroundTasks() { runningTasks: [], async init() { - backgroundTasksLog.info('Initializing background tasks monitor'); - await this.loadStats(); - await this.loadTasks(); - await this.loadRunningTasks(); + // Guard against multiple initialization + if (window._adminBackgroundTasksInitialized) return; + window._adminBackgroundTasksInitialized = true; - // Poll for updates every 5 seconds - this.pollInterval = setInterval(() => { - this.loadRunningTasks(); - if (this.runningTasks.length > 0) { - this.loadStats(); - } - }, 5000); + try { + backgroundTasksLog.info('Initializing background tasks monitor'); + await this.loadStats(); + await this.loadTasks(); + await this.loadRunningTasks(); + + // Poll for updates every 5 seconds + this.pollInterval = setInterval(() => { + this.loadRunningTasks(); + if (this.runningTasks.length > 0) { + this.loadStats(); + } + }, 5000); + } catch (error) { + backgroundTasksLog.error('Failed to initialize background tasks:', error); + } }, destroy() { diff --git a/static/admin/js/billing-history.js b/static/admin/js/billing-history.js index 2a46ed60..2043703a 100644 --- a/static/admin/js/billing-history.js +++ b/static/admin/js/billing-history.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/billing-history.js // noqa: JS-003 - Uses ...baseData which is data() with safety check diff --git a/static/admin/js/code-quality-dashboard.js b/static/admin/js/code-quality-dashboard.js index bfe384bb..04f0cd1a 100644 --- a/static/admin/js/code-quality-dashboard.js +++ b/static/admin/js/code-quality-dashboard.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch /** * Code Quality Dashboard Component * Manages the unified code quality dashboard page @@ -49,20 +50,28 @@ function codeQualityDashboard() { }, async init() { - // Check URL for validator_type parameter - const urlParams = new URLSearchParams(window.location.search); - const urlValidator = urlParams.get('validator_type'); - if (urlValidator && this.validatorTypes.includes(urlValidator)) { - this.selectedValidator = urlValidator; - } else { - // Ensure 'all' is explicitly set as default - this.selectedValidator = 'all'; + // Guard against multiple initialization + if (window._adminCodeQualityDashboardInitialized) return; + window._adminCodeQualityDashboardInitialized = true; + + try { + // Check URL for validator_type parameter + const urlParams = new URLSearchParams(window.location.search); + const urlValidator = urlParams.get('validator_type'); + if (urlValidator && this.validatorTypes.includes(urlValidator)) { + this.selectedValidator = urlValidator; + } else { + // Ensure 'all' is explicitly set as default + this.selectedValidator = 'all'; + } + + await this.loadStats(); + + // Check for any running scans on page load + await this.checkRunningScans(); + } catch (error) { + codeQualityLog.error('Failed to initialize code quality dashboard:', error); } - - await this.loadStats(); - - // Check for any running scans on page load - await this.checkRunningScans(); }, async checkRunningScans() { diff --git a/static/admin/js/code-quality-violations.js b/static/admin/js/code-quality-violations.js index 5c0da608..5a7f1884 100644 --- a/static/admin/js/code-quality-violations.js +++ b/static/admin/js/code-quality-violations.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch /** * Code Quality Violations List Component * Manages the violations list page with filtering and pagination diff --git a/static/admin/js/companies.js b/static/admin/js/companies.js index d252c8bd..8f200d10 100644 --- a/static/admin/js/companies.js +++ b/static/admin/js/companies.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/companies.js // ✅ Use centralized logger diff --git a/static/admin/js/company-detail.js b/static/admin/js/company-detail.js index a894255d..dd1b1428 100644 --- a/static/admin/js/company-detail.js +++ b/static/admin/js/company-detail.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/company-detail.js // Create custom logger for company detail diff --git a/static/admin/js/company-edit.js b/static/admin/js/company-edit.js index 424af655..5fa03614 100644 --- a/static/admin/js/company-edit.js +++ b/static/admin/js/company-edit.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/company-edit.js // Create custom logger for company edit diff --git a/static/admin/js/content-page-edit.js b/static/admin/js/content-page-edit.js index d12d6ce6..34ae3898 100644 --- a/static/admin/js/content-page-edit.js +++ b/static/admin/js/content-page-edit.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/content-page-edit.js // Use centralized logger diff --git a/static/admin/js/content-pages.js b/static/admin/js/content-pages.js index c4d9a132..f2669d08 100644 --- a/static/admin/js/content-pages.js +++ b/static/admin/js/content-pages.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/content-pages.js // Use centralized logger diff --git a/static/admin/js/customers.js b/static/admin/js/customers.js index 6da443e0..15a56429 100644 --- a/static/admin/js/customers.js +++ b/static/admin/js/customers.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/customers.js /** * Admin customer management page logic @@ -368,7 +369,7 @@ function adminCustomers() { customersLog.info(response.message); } catch (error) { customersLog.error('Failed to toggle status:', error); - alert(error.message || 'Failed to toggle customer status'); + Utils.showToast(error.message || 'Failed to toggle customer status', 'error'); } }, diff --git a/static/admin/js/dashboard.js b/static/admin/js/dashboard.js index 3658a6cf..e0c5a79e 100644 --- a/static/admin/js/dashboard.js +++ b/static/admin/js/dashboard.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/dashboard.js // ✅ Use centralized logger - ONE LINE! @@ -24,6 +25,13 @@ function adminDashboard() { * Initialize dashboard */ async init() { + // Guard against multiple initialization + if (window._dashboardInitialized) { + dashLog.warn('Dashboard already initialized, skipping...'); + return; + } + window._dashboardInitialized = true; + dashLog.info('=== DASHBOARD INITIALIZING ==='); dashLog.debug('Current URL:', window.location.href); dashLog.debug('Current pathname:', window.location.pathname); @@ -33,13 +41,6 @@ function adminDashboard() { if (token) { dashLog.debug('Token preview:', token.substring(0, 20) + '...'); } - - // Prevent multiple initializations - if (window._dashboardInitialized) { - dashLog.warn('Dashboard already initialized, skipping...'); - return; - } - window._dashboardInitialized = true; dashLog.debug('Dashboard initialization flag set'); const startTime = performance.now(); diff --git a/static/admin/js/imports.js b/static/admin/js/imports.js index 6f4a0ab0..67a56d8a 100644 --- a/static/admin/js/imports.js +++ b/static/admin/js/imports.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/imports.js /** * Admin platform monitoring - all import jobs diff --git a/static/admin/js/inventory.js b/static/admin/js/inventory.js index b1d8a767..42d940db 100644 --- a/static/admin/js/inventory.js +++ b/static/admin/js/inventory.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/inventory.js /** * Admin inventory management page logic @@ -553,20 +554,7 @@ function adminInventory() { formData.append('warehouse', this.importForm.warehouse || 'strassen'); formData.append('clear_existing', this.importForm.clear_existing); - const response = await fetch('/api/v1/admin/inventory/import', { - method: 'POST', - body: formData, - headers: { - 'Authorization': `Bearer ${localStorage.getItem('access_token') || ''}` - } - }); - - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.detail || 'Import failed'); - } - - this.importResult = await response.json(); + this.importResult = await apiClient.postFormData('/admin/inventory/import', formData); if (this.importResult.success) { adminInventoryLog.info('Import successful:', this.importResult); diff --git a/static/admin/js/login.js b/static/admin/js/login.js index bfb842e5..fd32247f 100644 --- a/static/admin/js/login.js +++ b/static/admin/js/login.js @@ -19,6 +19,10 @@ function adminLogin() { errors: {}, init() { + // Guard against multiple initialization + if (window._adminLoginInitialized) return; + window._adminLoginInitialized = true; + loginLog.info('=== LOGIN PAGE INITIALIZING ==='); loginLog.debug('Current pathname:', window.location.pathname); loginLog.debug('Current URL:', window.location.href); diff --git a/static/admin/js/logs.js b/static/admin/js/logs.js index 08d50a61..ddeb5631 100644 --- a/static/admin/js/logs.js +++ b/static/admin/js/logs.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/logs.js // noqa: JS-003 - Uses ...baseData which is data() with safety check diff --git a/static/admin/js/marketplace-letzshop.js b/static/admin/js/marketplace-letzshop.js index 1e9a86a0..97f70dc9 100644 --- a/static/admin/js/marketplace-letzshop.js +++ b/static/admin/js/marketplace-letzshop.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/marketplace-letzshop.js /** * Admin marketplace Letzshop management page logic diff --git a/static/admin/js/marketplace-product-detail.js b/static/admin/js/marketplace-product-detail.js index 858db912..7c8f86d6 100644 --- a/static/admin/js/marketplace-product-detail.js +++ b/static/admin/js/marketplace-product-detail.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/marketplace-product-detail.js /** * Admin marketplace product detail page logic diff --git a/static/admin/js/marketplace-products.js b/static/admin/js/marketplace-products.js index 3ca1f71e..2c6a91e0 100644 --- a/static/admin/js/marketplace-products.js +++ b/static/admin/js/marketplace-products.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/marketplace-products.js /** * Admin marketplace products page logic diff --git a/static/admin/js/marketplace.js b/static/admin/js/marketplace.js index bb6995f9..7934de98 100644 --- a/static/admin/js/marketplace.js +++ b/static/admin/js/marketplace.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/marketplace.js /** * Admin marketplace import page logic diff --git a/static/admin/js/messages.js b/static/admin/js/messages.js index 4591649e..539fead4 100644 --- a/static/admin/js/messages.js +++ b/static/admin/js/messages.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch /** * Admin Messages Page * @@ -66,17 +67,25 @@ function adminMessages(initialConversationId = null) { * Initialize component */ async init() { - messagesLog.debug('Initializing messages page'); - await this.loadConversations(); + // Guard against multiple initialization + if (window._adminMessagesInitialized) return; + window._adminMessagesInitialized = true; - if (this.selectedConversationId) { - await this.loadConversation(this.selectedConversationId); + try { + messagesLog.debug('Initializing messages page'); + await this.loadConversations(); + + if (this.selectedConversationId) { + await this.loadConversation(this.selectedConversationId); + } + + // Start polling for new messages + this.startPolling(); + } catch (error) { + messagesLog.error('Failed to initialize messages page:', error); + } finally { + this.loading = false; } - - this.loading = false; - - // Start polling for new messages - this.startPolling(); }, /** @@ -132,7 +141,7 @@ function adminMessages(initialConversationId = null) { messagesLog.debug(`Loaded ${this.conversations.length} conversations`); } catch (error) { messagesLog.error('Failed to load conversations:', error); - window.showToast?.('Failed to load conversations', 'error'); + Utils.showToast('Failed to load conversations', 'error'); } finally { this.loadingConversations = false; } @@ -188,7 +197,7 @@ function adminMessages(initialConversationId = null) { this.scrollToBottom(); } catch (error) { messagesLog.error('Failed to load conversation:', error); - window.showToast?.('Failed to load conversation', 'error'); + Utils.showToast('Failed to load conversation', 'error'); } finally { this.loadingMessages = false; } @@ -256,20 +265,7 @@ function adminMessages(initialConversationId = null) { formData.append('files', file); } - const response = await fetch(`/api/v1/admin/messages/${this.selectedConversationId}/messages`, { - method: 'POST', - body: formData, - headers: { - 'Authorization': `Bearer ${window.getAuthToken?.() || ''}` - } - }); - - if (!response.ok) { - const error = await response.json(); - throw new Error(error.detail || 'Failed to send message'); - } - - const message = await response.json(); + const message = await apiClient.postFormData(`/admin/messages/${this.selectedConversationId}/messages`, formData); // Add to messages if (this.selectedConversation) { @@ -291,7 +287,7 @@ function adminMessages(initialConversationId = null) { messagesLog.debug(`Sent message ${message.id}`); } catch (error) { messagesLog.error('Failed to send message:', error); - window.showToast?.(error.message || 'Failed to send message', 'error'); + Utils.showToast(error.message || 'Failed to send message', 'error'); } finally { this.sendingMessage = false; } @@ -320,10 +316,10 @@ function adminMessages(initialConversationId = null) { conv.is_closed = true; } - window.showToast?.('Conversation closed', 'success'); + Utils.showToast('Conversation closed', 'success'); } catch (error) { messagesLog.error('Failed to close conversation:', error); - window.showToast?.('Failed to close conversation', 'error'); + Utils.showToast('Failed to close conversation', 'error'); } }, @@ -344,10 +340,10 @@ function adminMessages(initialConversationId = null) { conv.is_closed = false; } - window.showToast?.('Conversation reopened', 'success'); + Utils.showToast('Conversation reopened', 'success'); } catch (error) { messagesLog.error('Failed to reopen conversation:', error); - window.showToast?.('Failed to reopen conversation', 'error'); + Utils.showToast('Failed to reopen conversation', 'error'); } }, @@ -371,7 +367,7 @@ function adminMessages(initialConversationId = null) { messagesLog.debug(`Loaded ${this.recipients.length} recipients`); } catch (error) { messagesLog.error('Failed to load recipients:', error); - window.showToast?.('Failed to load recipients', 'error'); + Utils.showToast('Failed to load recipients', 'error'); } finally { this.loadingRecipients = false; } @@ -420,10 +416,10 @@ function adminMessages(initialConversationId = null) { await this.loadConversations(); await this.selectConversation(response.id); - window.showToast?.('Conversation created', 'success'); + Utils.showToast('Conversation created', 'success'); } catch (error) { messagesLog.error('Failed to create conversation:', error); - window.showToast?.(error.message || 'Failed to create conversation', 'error'); + Utils.showToast(error.message || 'Failed to create conversation', 'error'); } finally { this.creatingConversation = false; } diff --git a/static/admin/js/notifications.js b/static/admin/js/notifications.js index c651ed42..12e742da 100644 --- a/static/admin/js/notifications.js +++ b/static/admin/js/notifications.js @@ -71,12 +71,21 @@ function adminNotifications() { * Initialize component */ async init() { - notificationsLog.debug('Initializing notifications page'); - await Promise.all([ - this.loadNotifications(), - this.loadAlertStats() - ]); - this.loading = false; + // Guard against multiple initialization + if (window._adminNotificationsInitialized) return; + window._adminNotificationsInitialized = true; + + try { + notificationsLog.debug('Initializing notifications page'); + await Promise.all([ + this.loadNotifications(), + this.loadAlertStats() + ]); + } catch (error) { + notificationsLog.error('Failed to initialize notifications page:', error); + } finally { + this.loading = false; + } }, // ============================================================================ @@ -105,7 +114,7 @@ function adminNotifications() { notificationsLog.debug(`Loaded ${this.notifications.length} notifications`); } catch (error) { notificationsLog.error('Failed to load notifications:', error); - window.showToast?.('Failed to load notifications', 'error'); + Utils.showToast('Failed to load notifications', 'error'); } finally { this.loadingNotifications = false; } @@ -122,10 +131,10 @@ function adminNotifications() { notification.is_read = true; this.stats.unread_count = Math.max(0, this.stats.unread_count - 1); - window.showToast?.('Notification marked as read', 'success'); + Utils.showToast('Notification marked as read', 'success'); } catch (error) { notificationsLog.error('Failed to mark as read:', error); - window.showToast?.('Failed to mark notification as read', 'error'); + Utils.showToast('Failed to mark notification as read', 'error'); } }, @@ -140,10 +149,10 @@ function adminNotifications() { this.notifications.forEach(n => n.is_read = true); this.stats.unread_count = 0; - window.showToast?.('All notifications marked as read', 'success'); + Utils.showToast('All notifications marked as read', 'success'); } catch (error) { notificationsLog.error('Failed to mark all as read:', error); - window.showToast?.('Failed to mark all as read', 'error'); + Utils.showToast('Failed to mark all as read', 'error'); } }, @@ -166,10 +175,10 @@ function adminNotifications() { this.stats.unread_count = Math.max(0, this.stats.unread_count - 1); } - window.showToast?.('Notification deleted', 'success'); + Utils.showToast('Notification deleted', 'success'); } catch (error) { notificationsLog.error('Failed to delete notification:', error); - window.showToast?.('Failed to delete notification', 'error'); + Utils.showToast('Failed to delete notification', 'error'); } }, @@ -216,7 +225,7 @@ function adminNotifications() { notificationsLog.debug(`Loaded ${this.alerts.length} alerts`); } catch (error) { notificationsLog.error('Failed to load alerts:', error); - window.showToast?.('Failed to load alerts', 'error'); + Utils.showToast('Failed to load alerts', 'error'); } finally { this.loadingAlerts = false; } @@ -263,10 +272,10 @@ function adminNotifications() { } this.alertStats.resolved_today++; - window.showToast?.('Alert resolved successfully', 'success'); + Utils.showToast('Alert resolved successfully', 'success'); } catch (error) { notificationsLog.error('Failed to resolve alert:', error); - window.showToast?.('Failed to resolve alert', 'error'); + Utils.showToast('Failed to resolve alert', 'error'); } }, diff --git a/static/admin/js/orders.js b/static/admin/js/orders.js index a80f502d..7db416c7 100644 --- a/static/admin/js/orders.js +++ b/static/admin/js/orders.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/orders.js /** * Admin orders management page logic diff --git a/static/admin/js/platform-health.js b/static/admin/js/platform-health.js index ae0f4830..b3b214fa 100644 --- a/static/admin/js/platform-health.js +++ b/static/admin/js/platform-health.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/platform-health.js /** * Admin platform health monitoring page logic diff --git a/static/admin/js/platform-homepage.js b/static/admin/js/platform-homepage.js index 14c86f4e..353824ff 100644 --- a/static/admin/js/platform-homepage.js +++ b/static/admin/js/platform-homepage.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/platform-homepage.js // Use centralized logger diff --git a/static/admin/js/settings.js b/static/admin/js/settings.js index 2d898151..2baea9b0 100644 --- a/static/admin/js/settings.js +++ b/static/admin/js/settings.js @@ -41,6 +41,10 @@ function adminSettings() { }, async init() { + // Guard against multiple initialization + if (window._adminSettingsInitialized) return; + window._adminSettingsInitialized = true; + try { settingsLog.info('=== SETTINGS PAGE INITIALIZING ==='); await Promise.all([ diff --git a/static/admin/js/subscriptions.js b/static/admin/js/subscriptions.js index df615a19..82ba2ec6 100644 --- a/static/admin/js/subscriptions.js +++ b/static/admin/js/subscriptions.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/subscriptions.js // noqa: JS-003 - Uses ...baseData which is data() with safety check diff --git a/static/admin/js/testing-dashboard.js b/static/admin/js/testing-dashboard.js index d9e1888d..183f7881 100644 --- a/static/admin/js/testing-dashboard.js +++ b/static/admin/js/testing-dashboard.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch /** * Testing Dashboard Component * Manages the pytest testing dashboard page @@ -53,11 +54,19 @@ function testingDashboard() { runs: [], async init() { - testingDashboardLog.info('Initializing testing dashboard'); - await this.loadStats(); - await this.loadRuns(); - // Check if there's a running test and resume polling - await this.checkForRunningTests(); + // Guard against multiple initialization + if (window._adminTestingDashboardInitialized) return; + window._adminTestingDashboardInitialized = true; + + try { + testingDashboardLog.info('Initializing testing dashboard'); + await this.loadStats(); + await this.loadRuns(); + // Check if there's a running test and resume polling + await this.checkForRunningTests(); + } catch (error) { + testingDashboardLog.error('Failed to initialize testing dashboard:', error); + } }, async checkForRunningTests() { diff --git a/static/admin/js/user-detail.js b/static/admin/js/user-detail.js index 8a8f8a44..1f00b704 100644 --- a/static/admin/js/user-detail.js +++ b/static/admin/js/user-detail.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/user-detail.js // Create custom logger for user detail diff --git a/static/admin/js/user-edit.js b/static/admin/js/user-edit.js index 9ab6fae5..a9b3d5ee 100644 --- a/static/admin/js/user-edit.js +++ b/static/admin/js/user-edit.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/user-edit.js // Create custom logger for user edit diff --git a/static/admin/js/users.js b/static/admin/js/users.js index db8a5e19..96b521d7 100644 --- a/static/admin/js/users.js +++ b/static/admin/js/users.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/users.js // ✅ Use centralized logger - ONE LINE! diff --git a/static/admin/js/vendor-create.js b/static/admin/js/vendor-create.js index 61d30ad1..63c0a685 100644 --- a/static/admin/js/vendor-create.js +++ b/static/admin/js/vendor-create.js @@ -43,8 +43,16 @@ function adminVendorCreate() { // Initialize async init() { - vendorCreateLog.info('Initializing vendor create page'); - await this.loadCompanies(); + // Guard against multiple initialization + if (window._adminVendorCreateInitialized) return; + window._adminVendorCreateInitialized = true; + + try { + vendorCreateLog.info('Initializing vendor create page'); + await this.loadCompanies(); + } catch (error) { + vendorCreateLog.error('Failed to initialize vendor create:', error); + } }, // Load companies for dropdown diff --git a/static/admin/js/vendor-detail.js b/static/admin/js/vendor-detail.js index bf896091..83823611 100644 --- a/static/admin/js/vendor-detail.js +++ b/static/admin/js/vendor-detail.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/vendor-detail.js // ✅ Use centralized logger - ONE LINE! diff --git a/static/admin/js/vendor-edit.js b/static/admin/js/vendor-edit.js index d5c2d352..c4b9ed6c 100644 --- a/static/admin/js/vendor-edit.js +++ b/static/admin/js/vendor-edit.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/vendor-edit.js // ✅ Use centralized logger - ONE LINE! diff --git a/static/admin/js/vendor-products.js b/static/admin/js/vendor-products.js index ac9a0dfc..fd6284f0 100644 --- a/static/admin/js/vendor-products.js +++ b/static/admin/js/vendor-products.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/vendor-products.js /** * Admin vendor products page logic diff --git a/static/admin/js/vendor-theme.js b/static/admin/js/vendor-theme.js index ab896793..45c6ba87 100644 --- a/static/admin/js/vendor-theme.js +++ b/static/admin/js/vendor-theme.js @@ -78,6 +78,10 @@ function adminVendorTheme() { // ==================================================================== async init() { + // Guard against multiple initialization + if (window._adminVendorThemeInitialized) return; + window._adminVendorThemeInitialized = true; + themeLog.info('Initializing vendor theme editor'); // Start performance timer diff --git a/static/admin/js/vendor-themes.js b/static/admin/js/vendor-themes.js index 3428f974..834bbf89 100644 --- a/static/admin/js/vendor-themes.js +++ b/static/admin/js/vendor-themes.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/vendor-themes.js /** * Admin vendor themes selection page diff --git a/static/admin/js/vendors.js b/static/admin/js/vendors.js index 17e083f6..01238202 100644 --- a/static/admin/js/vendors.js +++ b/static/admin/js/vendors.js @@ -1,3 +1,4 @@ +// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/vendors.js // ✅ Use centralized logger - ONE LINE! diff --git a/static/shared/js/api-client.js b/static/shared/js/api-client.js index 62679e58..8ba1e677 100644 --- a/static/shared/js/api-client.js +++ b/static/shared/js/api-client.js @@ -232,6 +232,103 @@ class APIClient { }); } + /** + * POST with FormData (for file uploads) + * Does not set Content-Type header - browser sets it with boundary + */ + async postFormData(endpoint, formData) { + const url = `${this.baseURL}${endpoint}`; + apiLog.info(`POST (FormData) ${url}`); + + const token = this.getToken(); + const headers = {}; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + try { + const startTime = Date.now(); + const response = await fetch(url, { + method: 'POST', + headers, + body: formData + }); + const duration = Date.now() - startTime; + + apiLog.info(`Response: ${response.status} ${response.statusText} (${duration}ms)`); + + let data; + try { + data = await response.json(); + } catch (parseError) { + apiLog.error('Failed to parse JSON response:', parseError); + throw new Error('Invalid JSON response from server'); + } + + if (response.status === 401) { + apiLog.warn('401 Unauthorized - Authentication failed'); + this.clearTokens(); + throw new Error(data.message || data.detail || 'Unauthorized'); + } + + if (!response.ok) { + throw new Error(data.detail || data.message || `Request failed with status ${response.status}`); + } + + return data; + } catch (error) { + apiLog.error('FormData request error:', error.message); + throw error; + } + } + + /** + * GET request that returns a Blob (for file downloads) + */ + async getBlob(endpoint) { + const url = `${this.baseURL}${endpoint}`; + apiLog.info(`GET (Blob) ${url}`); + + const token = this.getToken(); + const headers = {}; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + try { + const startTime = Date.now(); + const response = await fetch(url, { + method: 'GET', + headers + }); + const duration = Date.now() - startTime; + + apiLog.info(`Response: ${response.status} ${response.statusText} (${duration}ms)`); + + if (response.status === 401) { + apiLog.warn('401 Unauthorized - Authentication failed'); + this.clearTokens(); + throw new Error('Unauthorized'); + } + + if (!response.ok) { + let errorMessage = `Request failed with status ${response.status}`; + try { + const errorData = await response.json(); + errorMessage = errorData.detail || errorData.message || errorMessage; + } catch (e) { + // Response wasn't JSON + } + throw new Error(errorMessage); + } + + return response.blob(); + } catch (error) { + apiLog.error('Blob request error:', error.message); + throw error; + } + } + /** * Clear authentication tokens for current context only. * diff --git a/static/shared/js/feature-store.js b/static/shared/js/feature-store.js index 2eb5e82e..c155563e 100644 --- a/static/shared/js/feature-store.js +++ b/static/shared/js/feature-store.js @@ -53,8 +53,16 @@ * Called automatically when Alpine starts */ async init() { - log.debug('[FeatureStore] Initializing...'); - await this.loadFeatures(); + // Guard against multiple initialization + if (window._featureStoreInitialized) return; + window._featureStoreInitialized = true; + + try { + log.debug('[FeatureStore] Initializing...'); + await this.loadFeatures(); + } catch (error) { + log.error('[FeatureStore] Failed to initialize:', error); + } }, /** @@ -186,10 +194,14 @@ * Reload features (e.g., after tier change) */ async reload() { - this.loaded = false; - this.features = []; - this.featuresMap = {}; - await this.loadFeatures(); + try { + this.loaded = false; + this.features = []; + this.featuresMap = {}; + await this.loadFeatures(); + } catch (error) { + log.error('[FeatureStore] Failed to reload:', error); + } } }; diff --git a/static/shared/js/upgrade-prompts.js b/static/shared/js/upgrade-prompts.js index 9a18fab7..0a9804ca 100644 --- a/static/shared/js/upgrade-prompts.js +++ b/static/shared/js/upgrade-prompts.js @@ -344,8 +344,12 @@ * Reload usage data */ async reload() { - this.loaded = false; - await this.loadUsage(); + try { + this.loaded = false; + await this.loadUsage(); + } catch (error) { + log.error('[UpgradePrompts] Failed to reload:', error); + } } }; diff --git a/static/vendor/js/billing.js b/static/vendor/js/billing.js index f752ee6d..a174396d 100644 --- a/static/vendor/js/billing.js +++ b/static/vendor/js/billing.js @@ -29,22 +29,30 @@ function vendorBilling() { // Initialize async init() { - // Check URL params for success/cancel - const params = new URLSearchParams(window.location.search); - if (params.get('success') === 'true') { - this.showSuccessMessage = true; - window.history.replaceState({}, document.title, window.location.pathname); - } - if (params.get('cancelled') === 'true') { - this.showCancelMessage = true; - window.history.replaceState({}, document.title, window.location.pathname); - } - if (params.get('addon_success') === 'true') { - this.showAddonSuccessMessage = true; - window.history.replaceState({}, document.title, window.location.pathname); - } + // Guard against multiple initialization + if (window._vendorBillingInitialized) return; + window._vendorBillingInitialized = true; - await this.loadData(); + try { + // Check URL params for success/cancel + const params = new URLSearchParams(window.location.search); + if (params.get('success') === 'true') { + this.showSuccessMessage = true; + window.history.replaceState({}, document.title, window.location.pathname); + } + if (params.get('cancelled') === 'true') { + this.showCancelMessage = true; + window.history.replaceState({}, document.title, window.location.pathname); + } + if (params.get('addon_success') === 'true') { + this.showAddonSuccessMessage = true; + window.history.replaceState({}, document.title, window.location.pathname); + } + + await this.loadData(); + } catch (error) { + billingLog.error('Failed to initialize billing page:', error); + } }, async loadData() { diff --git a/static/vendor/js/content-page-edit.js b/static/vendor/js/content-page-edit.js index 58e608db..e980e62d 100644 --- a/static/vendor/js/content-page-edit.js +++ b/static/vendor/js/content-page-edit.js @@ -37,9 +37,6 @@ function vendorContentPageEditor(pageId) { // Initialize async init() { - contentPageEditLog.info('=== VENDOR CONTENT PAGE EDITOR INITIALIZING ==='); - contentPageEditLog.info('Page ID:', this.pageId); - // Prevent multiple initializations if (window._vendorContentPageEditInitialized) { contentPageEditLog.warn('Content page editor already initialized, skipping...'); @@ -47,17 +44,24 @@ function vendorContentPageEditor(pageId) { } window._vendorContentPageEditInitialized = true; - if (this.pageId) { - // Edit mode - load existing page - contentPageEditLog.group('Loading page for editing'); - await this.loadPage(); - contentPageEditLog.groupEnd(); - } else { - // Create mode - use default values - contentPageEditLog.info('Create mode - using default form values'); - } + try { + contentPageEditLog.info('=== VENDOR CONTENT PAGE EDITOR INITIALIZING ==='); + contentPageEditLog.info('Page ID:', this.pageId); - contentPageEditLog.info('=== VENDOR CONTENT PAGE EDITOR INITIALIZATION COMPLETE ==='); + if (this.pageId) { + // Edit mode - load existing page + contentPageEditLog.group('Loading page for editing'); + await this.loadPage(); + contentPageEditLog.groupEnd(); + } else { + // Create mode - use default values + contentPageEditLog.info('Create mode - using default form values'); + } + + contentPageEditLog.info('=== VENDOR CONTENT PAGE EDITOR INITIALIZATION COMPLETE ==='); + } catch (error) { + contentPageEditLog.error('Failed to initialize content page editor:', error); + } }, // Load existing page diff --git a/static/vendor/js/dashboard.js b/static/vendor/js/dashboard.js index 5bc26795..4eb50b58 100644 --- a/static/vendor/js/dashboard.js +++ b/static/vendor/js/dashboard.js @@ -38,13 +38,17 @@ function vendorDashboard() { } window._vendorDashboardInitialized = true; - // IMPORTANT: Call parent init first to set vendorCode from URL - const parentInit = data().init; - if (parentInit) { - await parentInit.call(this); - } + try { + // IMPORTANT: Call parent init first to set vendorCode from URL + const parentInit = data().init; + if (parentInit) { + await parentInit.call(this); + } - await this.loadDashboardData(); + await this.loadDashboardData(); + } catch (error) { + vendorDashLog.error('Failed to initialize dashboard:', error); + } }, async loadDashboardData() { @@ -94,7 +98,11 @@ function vendorDashboard() { }, async refresh() { - await this.loadDashboardData(); + try { + await this.loadDashboardData(); + } catch (error) { + vendorDashLog.error('Failed to refresh dashboard:', error); + } }, formatCurrency(amount) { diff --git a/static/vendor/js/invoices.js b/static/vendor/js/invoices.js index c0d8c79d..39e7765c 100644 --- a/static/vendor/js/invoices.js +++ b/static/vendor/js/invoices.js @@ -329,6 +329,7 @@ function vendorInvoices() { throw new Error('Not authenticated'); } + // noqa: js-008 - File download needs response headers for filename const response = await fetch(`/api/v1/vendor/invoices/${invoice.id}/pdf`, { method: 'GET', headers: { diff --git a/static/vendor/js/letzshop.js b/static/vendor/js/letzshop.js index 93a2df8d..06d1a413 100644 --- a/static/vendor/js/letzshop.js +++ b/static/vendor/js/letzshop.js @@ -438,6 +438,7 @@ function vendorLetzshop() { throw new Error('Not authenticated'); } + // noqa: js-008 - File download needs response headers for filename const response = await fetch(`/api/v1/vendor/letzshop/export?${params}`, { method: 'GET', headers: { diff --git a/static/vendor/js/login.js b/static/vendor/js/login.js index cc56dde6..38e0c645 100644 --- a/static/vendor/js/login.js +++ b/static/vendor/js/login.js @@ -24,24 +24,33 @@ function vendorLogin() { dark: false, async init() { - vendorLoginLog.info('=== VENDOR LOGIN PAGE INITIALIZING ==='); + // Guard against multiple initialization + if (window._vendorLoginInitialized) return; + window._vendorLoginInitialized = true; - // Load theme - const theme = localStorage.getItem('theme'); - if (theme === 'dark') { - this.dark = true; - } - vendorLoginLog.debug('Dark mode:', this.dark); + try { + vendorLoginLog.info('=== VENDOR LOGIN PAGE INITIALIZING ==='); - // Get vendor code from URL path - const pathSegments = window.location.pathname.split('/').filter(Boolean); - if (pathSegments[0] === 'vendor' && pathSegments[1]) { - this.vendorCode = pathSegments[1]; - vendorLoginLog.debug('Vendor code from URL:', this.vendorCode); - await this.loadVendor(); + // Load theme + const theme = localStorage.getItem('theme'); + if (theme === 'dark') { + this.dark = true; + } + vendorLoginLog.debug('Dark mode:', this.dark); + + // Get vendor code from URL path + const pathSegments = window.location.pathname.split('/').filter(Boolean); + if (pathSegments[0] === 'vendor' && pathSegments[1]) { + this.vendorCode = pathSegments[1]; + vendorLoginLog.debug('Vendor code from URL:', this.vendorCode); + await this.loadVendor(); + } + this.checked = true; + vendorLoginLog.info('=== VENDOR LOGIN PAGE INITIALIZATION COMPLETE ==='); + } catch (error) { + vendorLoginLog.error('Failed to initialize login page:', error); + this.checked = true; } - this.checked = true; - vendorLoginLog.info('=== VENDOR LOGIN PAGE INITIALIZATION COMPLETE ==='); }, async loadVendor() { diff --git a/static/vendor/js/messages.js b/static/vendor/js/messages.js index cb45f587..c7e59323 100644 --- a/static/vendor/js/messages.js +++ b/static/vendor/js/messages.js @@ -61,20 +61,28 @@ function vendorMessages(initialConversationId = null) { * Initialize component */ async init() { - messagesLog.debug('Initializing vendor messages page'); - await Promise.all([ - this.loadConversations(), - this.loadRecipients() - ]); + // Guard against multiple initialization + if (window._vendorMessagesInitialized) return; + window._vendorMessagesInitialized = true; - if (this.selectedConversationId) { - await this.loadConversation(this.selectedConversationId); + try { + messagesLog.debug('Initializing vendor messages page'); + await Promise.all([ + this.loadConversations(), + this.loadRecipients() + ]); + + if (this.selectedConversationId) { + await this.loadConversation(this.selectedConversationId); + } + + // Start polling for new messages + this.startPolling(); + } catch (error) { + messagesLog.error('Failed to initialize messages page:', error); + } finally { + this.loading = false; } - - this.loading = false; - - // Start polling for new messages - this.startPolling(); }, /** @@ -128,7 +136,7 @@ function vendorMessages(initialConversationId = null) { messagesLog.debug(`Loaded ${this.conversations.length} conversations`); } catch (error) { messagesLog.error('Failed to load conversations:', error); - window.showToast?.('Failed to load conversations', 'error'); + Utils.showToast('Failed to load conversations', 'error'); } finally { this.loadingConversations = false; } @@ -177,7 +185,7 @@ function vendorMessages(initialConversationId = null) { this.scrollToBottom(); } catch (error) { messagesLog.error('Failed to load conversation:', error); - window.showToast?.('Failed to load conversation', 'error'); + Utils.showToast('Failed to load conversation', 'error'); } finally { this.loadingMessages = false; } @@ -231,20 +239,7 @@ function vendorMessages(initialConversationId = null) { const formData = new FormData(); formData.append('content', this.replyContent); - const response = await fetch(`/api/v1/vendor/messages/${this.selectedConversationId}/messages`, { - method: 'POST', - body: formData, - headers: { - 'Authorization': `Bearer ${window.getAuthToken?.() || ''}` - } - }); - - if (!response.ok) { - const error = await response.json(); - throw new Error(error.detail || 'Failed to send message'); - } - - const message = await response.json(); + const message = await apiClient.postFormData(`/vendor/messages/${this.selectedConversationId}/messages`, formData); // Add to messages if (this.selectedConversation) { @@ -260,7 +255,7 @@ function vendorMessages(initialConversationId = null) { this.scrollToBottom(); } catch (error) { messagesLog.error('Failed to send message:', error); - window.showToast?.(error.message || 'Failed to send message', 'error'); + Utils.showToast(error.message || 'Failed to send message', 'error'); } finally { this.sendingMessage = false; } @@ -286,10 +281,10 @@ function vendorMessages(initialConversationId = null) { const conv = this.conversations.find(c => c.id === this.selectedConversationId); if (conv) conv.is_closed = true; - window.showToast?.('Conversation closed', 'success'); + Utils.showToast('Conversation closed', 'success'); } catch (error) { messagesLog.error('Failed to close conversation:', error); - window.showToast?.('Failed to close conversation', 'error'); + Utils.showToast('Failed to close conversation', 'error'); } }, @@ -307,10 +302,10 @@ function vendorMessages(initialConversationId = null) { const conv = this.conversations.find(c => c.id === this.selectedConversationId); if (conv) conv.is_closed = false; - window.showToast?.('Conversation reopened', 'success'); + Utils.showToast('Conversation reopened', 'success'); } catch (error) { messagesLog.error('Failed to reopen conversation:', error); - window.showToast?.('Failed to reopen conversation', 'error'); + Utils.showToast('Failed to reopen conversation', 'error'); } }, @@ -354,10 +349,10 @@ function vendorMessages(initialConversationId = null) { await this.loadConversations(); await this.selectConversation(response.id); - window.showToast?.('Conversation created', 'success'); + Utils.showToast('Conversation created', 'success'); } catch (error) { messagesLog.error('Failed to create conversation:', error); - window.showToast?.(error.message || 'Failed to create conversation', 'error'); + Utils.showToast(error.message || 'Failed to create conversation', 'error'); } finally { this.creatingConversation = false; } diff --git a/static/vendor/js/onboarding.js b/static/vendor/js/onboarding.js index 2e16608a..8a84397e 100644 --- a/static/vendor/js/onboarding.js +++ b/static/vendor/js/onboarding.js @@ -345,7 +345,15 @@ function vendorOnboarding(initialLang = 'en') { // Initialize async init() { - await this.loadStatus(); + // Guard against multiple initialization + if (window._vendorOnboardingInitialized) return; + window._vendorOnboardingInitialized = true; + + try { + await this.loadStatus(); + } catch (error) { + onboardingLog.error('Failed to initialize onboarding:', error); + } }, // Load current onboarding status