// noqa: js-006 - async init pattern is safe, loadData has try/catch // static/admin/js/store-detail.js // ✅ Use centralized logger - ONE LINE! // Create custom logger for store detail const detailLog = window.LogConfig.createLogger('STORE-DETAIL'); function adminStoreDetail() { return { // Inherit base layout functionality from init-alpine.js ...data(), // Store detail page specific state currentPage: 'store-detail', store: null, subscriptions: [], loading: false, error: null, storeCode: null, showDeleteStoreModal: false, showDeleteStoreFinalModal: false, // Domain management state domains: [], domainsLoading: false, showAddDomainForm: false, domainSaving: false, newDomain: { domain: '', platform_id: '' }, // Custom subdomain management state customSubdomains: [], customSubdomainsLoading: false, editingSubdomainPlatformId: null, editingSubdomainValue: '', subdomainSaving: false, // Initialize async init() { // Load i18n translations await I18n.loadModule('tenancy'); detailLog.info('=== STORE DETAIL PAGE INITIALIZING ==='); // Prevent multiple initializations if (window._storeDetailInitialized) { detailLog.warn('Store detail page already initialized, skipping...'); return; } window._storeDetailInitialized = true; // Get store code from URL const path = window.location.pathname; const match = path.match(/\/admin\/stores\/([^\/]+)$/); if (match) { this.storeCode = match[1]; detailLog.info('Viewing store:', this.storeCode); await this.loadStore(); // Load subscription and domains after store is loaded if (this.store?.id) { await Promise.all([ this.loadSubscriptions(), this.loadDomains(), this.loadCustomSubdomains(), ]); } } else { detailLog.error('No store code in URL'); this.error = 'Invalid store URL'; Utils.showToast(I18n.t('tenancy.messages.invalid_store_url'), 'error'); } detailLog.info('=== STORE DETAIL PAGE INITIALIZATION COMPLETE ==='); }, // Load store data async loadStore() { detailLog.info('Loading store details...'); this.loading = true; this.error = null; try { const url = `/admin/stores/${this.storeCode}`; 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 Store Details', duration); this.store = response; detailLog.info(`Store loaded in ${duration}ms`, { store_code: this.store.store_code, name: this.store.name, is_verified: this.store.is_verified, is_active: this.store.is_active }); detailLog.debug('Full store data:', this.store); } catch (error) { window.LogConfig.logError(error, 'Load Store Details'); this.error = error.message || 'Failed to load store details'; Utils.showToast(I18n.t('tenancy.messages.failed_to_load_store_details'), 'error'); } finally { this.loading = false; } }, // Format date (matches dashboard pattern) formatDate(dateString) { if (!dateString) { detailLog.debug('formatDate called with empty dateString'); return '-'; } const formatted = Utils.formatDate(dateString); detailLog.debug(`Date formatted: ${dateString} -> ${formatted}`); return formatted; }, // Load subscriptions data for this store via convenience endpoint async loadSubscriptions() { if (!this.store?.id) { detailLog.warn('Cannot load subscriptions: no store ID'); return; } detailLog.info('Loading subscriptions for store:', this.store.id); try { const url = `/admin/subscriptions/store/${this.store.id}`; window.LogConfig.logApiCall('GET', url, null, 'request'); const response = await apiClient.get(url); window.LogConfig.logApiCall('GET', url, response, 'response'); this.subscriptions = response.subscriptions || []; detailLog.info('Subscriptions loaded:', { count: this.subscriptions.length, platforms: this.subscriptions.map(e => e.platform_name) }); } catch (error) { // 404 means no subscription exists - that's OK if (error.status === 404) { detailLog.info('No subscriptions found for store'); this.subscriptions = []; } else { detailLog.warn('Failed to load subscriptions:', error.message); } } }, // 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'; }, // Prompt delete store (first step of double confirm) promptDeleteStore() { detailLog.info('Delete store requested:', this.storeCode); this.showDeleteStoreModal = true; }, // Confirm first step, show final confirmation confirmDeleteStoreStep() { detailLog.info('First delete confirmation accepted, showing final confirmation'); this.showDeleteStoreFinalModal = true; }, // Delete store async deleteStore() { try { const url = `/admin/stores/${this.storeCode}?confirm=true`; window.LogConfig.logApiCall('DELETE', url, null, 'request'); detailLog.info('Deleting store:', this.storeCode); await apiClient.delete(url); window.LogConfig.logApiCall('DELETE', url, null, 'response'); Utils.showToast(I18n.t('tenancy.messages.store_deleted_successfully'), 'success'); detailLog.info('Store deleted successfully'); // Redirect to stores list setTimeout(() => window.location.href = '/admin/stores', 1500); } catch (error) { window.LogConfig.logError(error, 'Delete Store'); Utils.showToast(error.message || 'Failed to delete store', 'error'); } }, // ==================================================================== // DOMAIN MANAGEMENT // ==================================================================== async loadDomains() { if (!this.store?.id) return; this.domainsLoading = true; try { const url = `/admin/stores/${this.store.id}/domains`; const response = await apiClient.get(url); this.domains = response.domains || []; detailLog.info('Domains loaded:', this.domains.length); } catch (error) { if (error.status === 404) { this.domains = []; } else { detailLog.warn('Failed to load domains:', error.message); } } finally { this.domainsLoading = false; } }, async addDomain() { if (!this.newDomain.domain || this.domainSaving) return; this.domainSaving = true; try { const payload = { domain: this.newDomain.domain }; if (this.newDomain.platform_id) { payload.platform_id = parseInt(this.newDomain.platform_id); } await apiClient.post(`/admin/stores/${this.store.id}/domains`, payload); Utils.showToast('Domain added successfully', 'success'); this.showAddDomainForm = false; this.newDomain = { domain: '', platform_id: '' }; await this.loadDomains(); } catch (error) { Utils.showToast(error.message || 'Failed to add domain', 'error'); } finally { this.domainSaving = false; } }, async verifyDomain(domainId) { try { const response = await apiClient.post(`/admin/stores/domains/${domainId}/verify`); Utils.showToast(response.message || 'Domain verified!', 'success'); await this.loadDomains(); } catch (error) { Utils.showToast(error.message || 'Verification failed — check DNS records', 'error'); } }, async toggleDomainActive(domainId, activate) { try { await apiClient.put(`/admin/stores/domains/${domainId}`, { is_active: activate }); Utils.showToast(activate ? 'Domain activated' : 'Domain deactivated', 'success'); await this.loadDomains(); } catch (error) { Utils.showToast(error.message || 'Failed to update domain', 'error'); } }, async setDomainPrimary(domainId) { try { await apiClient.put(`/admin/stores/domains/${domainId}`, { is_primary: true }); Utils.showToast('Domain set as primary', 'success'); await this.loadDomains(); } catch (error) { Utils.showToast(error.message || 'Failed to set primary domain', 'error'); } }, async deleteDomain(domainId, domainName) { if (!confirm(`Delete domain "${domainName}"? This cannot be undone.`)) return; try { await apiClient.delete(`/admin/stores/domains/${domainId}`); Utils.showToast('Domain deleted', 'success'); await this.loadDomains(); } catch (error) { Utils.showToast(error.message || 'Failed to delete domain', 'error'); } }, // ==================================================================== // CUSTOM SUBDOMAIN MANAGEMENT // ==================================================================== async loadCustomSubdomains() { if (!this.store?.id) return; this.customSubdomainsLoading = true; try { const url = `/admin/stores/${this.store.id}/custom-subdomains`; const response = await apiClient.get(url); this.customSubdomains = response.subdomains || []; detailLog.info('Custom subdomains loaded:', this.customSubdomains.length); } catch (error) { if (error.status === 404) { this.customSubdomains = []; } else { detailLog.warn('Failed to load custom subdomains:', error.message); } } finally { this.customSubdomainsLoading = false; } }, startEditSubdomain(entry) { this.editingSubdomainPlatformId = entry.platform_id; this.editingSubdomainValue = entry.custom_subdomain || ''; }, cancelEditSubdomain() { this.editingSubdomainPlatformId = null; this.editingSubdomainValue = ''; }, async saveCustomSubdomain(platformId) { if (!this.editingSubdomainValue || this.subdomainSaving) return; this.subdomainSaving = true; try { await apiClient.put( `/admin/stores/${this.store.id}/custom-subdomains/${platformId}`, { subdomain: this.editingSubdomainValue.trim().toLowerCase() } ); Utils.showToast('Custom subdomain saved', 'success'); this.cancelEditSubdomain(); await this.loadCustomSubdomains(); } catch (error) { Utils.showToast(error.message || 'Failed to save subdomain', 'error'); } finally { this.subdomainSaving = false; } }, async clearCustomSubdomain(platformId, subdomainName) { if (!confirm(`Clear custom subdomain "${subdomainName}"? The store will use its default subdomain on this platform.`)) return; try { await apiClient.delete( `/admin/stores/${this.store.id}/custom-subdomains/${platformId}` ); Utils.showToast('Custom subdomain cleared', 'success'); await this.loadCustomSubdomains(); } catch (error) { Utils.showToast(error.message || 'Failed to clear subdomain', 'error'); } }, // Refresh store data async refresh() { detailLog.info('=== STORE REFRESH TRIGGERED ==='); await this.loadStore(); Utils.showToast(I18n.t('tenancy.messages.store_details_refreshed'), 'success'); detailLog.info('=== STORE REFRESH COMPLETE ==='); } }; } detailLog.info('Store detail module loaded');