// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/merchant-detail.js // Create custom logger for merchant detail const merchantDetailLog = window.LogConfig.createLogger('MERCHANT-DETAIL'); function adminMerchantDetail() { return { // Inherit base layout functionality from init-alpine.js ...data(), // Merchant detail page specific state currentPage: 'merchant-detail', merchant: null, loading: false, error: null, merchantId: null, // Subscription state subscription: null, subscriptionTier: null, usageMetrics: [], tiers: [], platformId: null, showCreateSubscriptionModal: false, createForm: { tier_code: 'essential', status: 'trial', trial_days: 14, is_annual: false }, creatingSubscription: false, // Initialize async init() { // Load i18n translations await I18n.loadModule('tenancy'); merchantDetailLog.info('=== MERCHANT DETAIL PAGE INITIALIZING ==='); // Prevent multiple initializations if (window._merchantDetailInitialized) { merchantDetailLog.warn('Merchant detail page already initialized, skipping...'); return; } window._merchantDetailInitialized = true; // Get merchant ID from URL const path = window.location.pathname; const match = path.match(/\/admin\/merchants\/(\d+)$/); if (match) { this.merchantId = match[1]; merchantDetailLog.info('Viewing merchant:', this.merchantId); await this.loadMerchant(); await this.loadPlatforms(); if (this.platformId) { await this.loadSubscription(); } } else { merchantDetailLog.error('No merchant ID in URL'); this.error = 'Invalid merchant URL'; Utils.showToast(I18n.t('tenancy.messages.invalid_merchant_url'), 'error'); } merchantDetailLog.info('=== MERCHANT DETAIL PAGE INITIALIZATION COMPLETE ==='); }, // Load merchant data async loadMerchant() { merchantDetailLog.info('Loading merchant details...'); this.loading = true; this.error = null; try { const url = `/admin/merchants/${this.merchantId}`; window.LogConfig.logApiCall('GET', url, null, 'request'); const startTime = performance.now(); const response = await apiClient.get(url); const duration = performance.now() - startTime; window.LogConfig.logApiCall('GET', url, response, 'response'); window.LogConfig.logPerformance('Load Merchant Details', duration); this.merchant = response; merchantDetailLog.info(`Merchant loaded in ${duration}ms`, { id: this.merchant.id, name: this.merchant.name, is_verified: this.merchant.is_verified, is_active: this.merchant.is_active, store_count: this.merchant.store_count }); merchantDetailLog.debug('Full merchant data:', this.merchant); } catch (error) { window.LogConfig.logError(error, 'Load Merchant Details'); this.error = error.message || 'Failed to load merchant details'; Utils.showToast(I18n.t('tenancy.messages.failed_to_load_merchant_details'), 'error'); } finally { this.loading = false; } }, // Load platforms and find OMS platform ID async loadPlatforms() { try { const response = await apiClient.get('/admin/platforms'); const platforms = response.platforms || []; const oms = platforms.find(p => p.code === 'oms'); if (oms) { this.platformId = oms.id; merchantDetailLog.info('OMS platform resolved:', this.platformId); } else { merchantDetailLog.warn('OMS platform not found'); } } catch (error) { merchantDetailLog.warn('Failed to load platforms:', error.message); } }, // Load subscription for this merchant async loadSubscription() { if (!this.merchantId || !this.platformId) return; merchantDetailLog.info('Loading subscription for merchant:', this.merchantId); try { const url = `/admin/subscriptions/merchants/${this.merchantId}/platforms/${this.platformId}`; window.LogConfig.logApiCall('GET', url, null, 'request'); const response = await apiClient.get(url); window.LogConfig.logApiCall('GET', url, response, 'response'); this.subscription = response.subscription || response; this.subscriptionTier = response.tier || null; this.usageMetrics = response.features || []; merchantDetailLog.info('Subscription loaded:', { tier: this.subscription?.tier, status: this.subscription?.status, features_count: this.usageMetrics.length }); } catch (error) { if (error.status === 404) { merchantDetailLog.info('No subscription found for merchant'); this.subscription = null; this.usageMetrics = []; } else { merchantDetailLog.warn('Failed to load subscription:', error.message); } } }, // Load available subscription tiers async loadTiers() { if (this.tiers.length > 0) return; try { const response = await apiClient.get('/admin/subscriptions/tiers'); this.tiers = response.tiers || []; merchantDetailLog.info('Loaded tiers:', this.tiers.length); } catch (error) { merchantDetailLog.warn('Failed to load tiers:', error.message); } }, // Open create subscription modal async openCreateSubscriptionModal() { await this.loadTiers(); this.createForm = { tier_code: 'essential', status: 'trial', trial_days: 14, is_annual: false }; this.showCreateSubscriptionModal = true; }, // Create subscription for this merchant async createSubscription() { if (!this.merchantId || !this.platformId) return; this.creatingSubscription = true; merchantDetailLog.info('Creating subscription for merchant:', this.merchantId); try { const url = `/admin/subscriptions/merchants/${this.merchantId}/platforms/${this.platformId}`; const payload = { merchant_id: parseInt(this.merchantId), platform_id: this.platformId, tier_code: this.createForm.tier_code, status: this.createForm.status, trial_days: this.createForm.status === 'trial' ? parseInt(this.createForm.trial_days) : 0, is_annual: this.createForm.is_annual }; window.LogConfig.logApiCall('POST', url, payload, 'request'); const response = await apiClient.post(url, payload); window.LogConfig.logApiCall('POST', url, response, 'response'); this.showCreateSubscriptionModal = false; Utils.showToast('Subscription created successfully', 'success'); merchantDetailLog.info('Subscription created'); await this.loadSubscription(); } catch (error) { window.LogConfig.logError(error, 'Create Subscription'); Utils.showToast(error.message || 'Failed to create subscription', 'error'); } finally { this.creatingSubscription = false; } }, // Get usage bar color based on percentage getUsageBarColor(current, limit) { if (!limit || limit === 0) return 'bg-blue-500'; const percent = (current / limit) * 100; if (percent >= 90) return 'bg-red-500'; if (percent >= 75) return 'bg-yellow-500'; return 'bg-green-500'; }, // Format tier price for display formatTierPrice(tier) { if (!tier.price_monthly_cents) return 'Custom'; return `€${(tier.price_monthly_cents / 100).toFixed(2)}/mo`; }, // Format date (matches dashboard pattern) formatDate(dateString) { if (!dateString) { merchantDetailLog.debug('formatDate called with empty dateString'); return '-'; } const formatted = Utils.formatDate(dateString); merchantDetailLog.debug(`Date formatted: ${dateString} -> ${formatted}`); return formatted; }, // Delete merchant async deleteMerchant() { merchantDetailLog.info('Delete merchant requested:', this.merchantId); if (this.merchant?.store_count > 0) { Utils.showToast(`Cannot delete merchant with ${this.merchant.store_count} store(s). Delete stores first.`, 'error'); return; } if (!confirm(`Are you sure you want to delete merchant "${this.merchant.name}"?\n\nThis action cannot be undone.`)) { merchantDetailLog.info('Delete cancelled by user'); return; } // Second confirmation for safety if (!confirm(`FINAL CONFIRMATION\n\nAre you absolutely sure you want to delete "${this.merchant.name}"?`)) { merchantDetailLog.info('Delete cancelled by user (second confirmation)'); return; } try { const url = `/admin/merchants/${this.merchantId}?confirm=true`; window.LogConfig.logApiCall('DELETE', url, null, 'request'); merchantDetailLog.info('Deleting merchant:', this.merchantId); await apiClient.delete(url); window.LogConfig.logApiCall('DELETE', url, null, 'response'); Utils.showToast(I18n.t('tenancy.messages.merchant_deleted_successfully'), 'success'); merchantDetailLog.info('Merchant deleted successfully'); // Redirect to merchants list setTimeout(() => window.location.href = '/admin/merchants', 1500); } catch (error) { window.LogConfig.logError(error, 'Delete Merchant'); Utils.showToast(error.message || 'Failed to delete merchant', 'error'); } }, // Refresh merchant data async refresh() { merchantDetailLog.info('=== MERCHANT REFRESH TRIGGERED ==='); await this.loadMerchant(); if (this.platformId) { await this.loadSubscription(); } Utils.showToast(I18n.t('tenancy.messages.merchant_details_refreshed'), 'success'); merchantDetailLog.info('=== MERCHANT REFRESH COMPLETE ==='); } }; } merchantDetailLog.info('Merchant detail module loaded');