fix: resolve all JS architecture violations (JS-005 through JS-009)

Fixed 89 violations across vendor, admin, and shared JavaScript files:

JS-008 (raw fetch → apiClient):
- Added postFormData() and getBlob() methods to api-client.js
- Updated inventory.js, messages.js to use apiClient.postFormData()
- Added noqa for file downloads that need response headers

JS-009 (window.showToast → Utils.showToast):
- Updated admin/messages.js, notifications.js, vendor/messages.js
- Replaced alert() in customers.js

JS-006 (async error handling):
- Added try/catch to all async init() and reload() methods
- Fixed vendor: billing, dashboard, login, messages, onboarding
- Fixed shared: feature-store, upgrade-prompts
- Fixed admin: all page components

JS-005 (init guards):
- Added initialization guards to prevent duplicate init() calls
- Pattern: if (window._componentInitialized) return;

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-31 21:32:19 +01:00
parent c8fd09d16f
commit 265c71f597
48 changed files with 410 additions and 196 deletions

View File

@@ -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() {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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