// 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, // Modal state showDeleteMerchantModal: false, showDeleteMerchantFinalModal: false, // Subscription state platforms: [], subscriptions: [], tiers: [], tiersForPlatformId: null, showCreateSubscriptionModal: false, createForm: { platform_id: null, 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 Promise.all([this.loadPlatforms(), this.loadSubscriptions()]); } 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 all available platforms async loadPlatforms() { try { const response = await apiClient.get('/admin/platforms'); this.platforms = (response.platforms || []).map(p => ({ id: p.id, name: p.name, code: p.code })); merchantDetailLog.info('Platforms loaded:', this.platforms.length); } catch (error) { merchantDetailLog.warn('Failed to load platforms:', error.message); } }, // Load all subscriptions for this merchant in a single call async loadSubscriptions() { if (!this.merchantId) return; merchantDetailLog.info('Loading subscriptions for merchant:', this.merchantId); this.subscriptions = []; try { const url = `/admin/subscriptions/merchants/${this.merchantId}`; const response = await apiClient.get(url); this.subscriptions = response.subscriptions || []; } catch (error) { merchantDetailLog.warn('Failed to load subscriptions:', error.message); } merchantDetailLog.info('Subscriptions loaded:', { count: this.subscriptions.length, platforms: this.subscriptions.map(e => e.platform_name) }); }, // Load available subscription tiers for a platform async loadTiers(platformId) { if (this.tiers.length > 0 && this.tiersForPlatformId === platformId) return; try { const url = platformId ? `/admin/subscriptions/tiers?platform_id=${platformId}` : '/admin/subscriptions/tiers'; const response = await apiClient.get(url); this.tiers = response.tiers || []; this.tiersForPlatformId = platformId; merchantDetailLog.info('Loaded tiers:', this.tiers.length); } catch (error) { merchantDetailLog.warn('Failed to load tiers:', error.message); } }, // Open create subscription modal (only show platforms without existing subscriptions) async openCreateSubscriptionModal() { const usedPlatformIds = this.subscriptions.map(e => e.platform_id); const available = this.platforms.filter(p => !usedPlatformIds.includes(p.id)); if (available.length === 0) { Utils.showToast('All platforms already have subscriptions', 'info'); return; } this.createForm = { platform_id: available[0].id, tier_code: 'essential', status: 'trial', trial_days: 14, is_annual: false }; await this.loadTiers(available[0].id); this.showCreateSubscriptionModal = true; }, // Reload tiers when platform changes in create modal async onCreatePlatformChange() { this.tiers = []; this.tiersForPlatformId = null; await this.loadTiers(this.createForm.platform_id); }, // Create subscription for this merchant async createSubscription() { if (!this.merchantId || !this.createForm.platform_id) return; this.creatingSubscription = true; const platformId = this.createForm.platform_id; merchantDetailLog.info('Creating subscription for merchant:', this.merchantId, 'platform:', platformId); try { const url = `/admin/subscriptions/merchants/${this.merchantId}/platforms/${platformId}`; const payload = { merchant_id: parseInt(this.merchantId), platform_id: 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.loadSubscriptions(); } 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; }, // Prompt delete merchant (first step of double confirm) promptDeleteMerchant() { 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; } this.showDeleteMerchantModal = true; }, // Confirm first step, show final confirmation confirmDeleteMerchantStep() { this.showDeleteMerchantFinalModal = true; }, // Delete merchant async deleteMerchant() { 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(); await this.loadSubscriptions(); Utils.showToast(I18n.t('tenancy.messages.merchant_details_refreshed'), 'success'); merchantDetailLog.info('=== MERCHANT REFRESH COMPLETE ==='); } }; } merchantDetailLog.info('Merchant detail module loaded');