fix(subscriptions): fix subscription UI and API after store→merchant migration
Store detail page now shows all platform subscriptions instead of always "No Subscription Found". Subscriptions listing page renamed from Store to Merchant throughout (template, JS, menu, i18n) with Platform column added. Tiers API supports platform_id filtering. Merchant detail page no longer hardcodes 'oms' platform — loads all platforms, shows subscription cards per platform with labels, and the Create Subscription modal includes a platform selector with platform-filtered tiers. Create button always accessible in Quick Actions. Edit modal on /admin/subscriptions loads tiers from API filtered by platform instead of hardcoded options, sends tier_code (not tier) to match PATCH schema, and shows platform context. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,13 +17,12 @@ function adminMerchantDetail() {
|
||||
merchantId: null,
|
||||
|
||||
// Subscription state
|
||||
subscription: null,
|
||||
subscriptionTier: null,
|
||||
usageMetrics: [],
|
||||
platforms: [],
|
||||
subscriptions: [],
|
||||
tiers: [],
|
||||
platformId: null,
|
||||
tiersForPlatformId: null,
|
||||
showCreateSubscriptionModal: false,
|
||||
createForm: { tier_code: 'essential', status: 'trial', trial_days: 14, is_annual: false },
|
||||
createForm: { platform_id: null, tier_code: 'essential', status: 'trial', trial_days: 14, is_annual: false },
|
||||
creatingSubscription: false,
|
||||
|
||||
// Initialize
|
||||
@@ -49,9 +48,7 @@ function adminMerchantDetail() {
|
||||
merchantDetailLog.info('Viewing merchant:', this.merchantId);
|
||||
await this.loadMerchant();
|
||||
await this.loadPlatforms();
|
||||
if (this.platformId) {
|
||||
await this.loadSubscription();
|
||||
}
|
||||
await this.loadSubscriptions();
|
||||
} else {
|
||||
merchantDetailLog.error('No merchant ID in URL');
|
||||
this.error = 'Invalid merchant URL';
|
||||
@@ -98,64 +95,61 @@ function adminMerchantDetail() {
|
||||
}
|
||||
},
|
||||
|
||||
// Load platforms and find OMS platform ID
|
||||
// Load all available platforms
|
||||
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');
|
||||
}
|
||||
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 subscription for this merchant
|
||||
async loadSubscription() {
|
||||
if (!this.merchantId || !this.platformId) return;
|
||||
// Load subscriptions for all platforms
|
||||
async loadSubscriptions() {
|
||||
if (!this.merchantId || this.platforms.length === 0) return;
|
||||
|
||||
merchantDetailLog.info('Loading subscription for merchant:', this.merchantId);
|
||||
merchantDetailLog.info('Loading subscriptions for merchant:', this.merchantId);
|
||||
this.subscriptions = [];
|
||||
|
||||
try {
|
||||
const url = `/admin/subscriptions/merchants/${this.merchantId}/platforms/${this.platformId}`;
|
||||
window.LogConfig.logApiCall('GET', url, null, 'request');
|
||||
for (const platform of this.platforms) {
|
||||
try {
|
||||
const url = `/admin/subscriptions/merchants/${this.merchantId}/platforms/${platform.id}`;
|
||||
const response = await apiClient.get(url);
|
||||
const sub = response.subscription || response;
|
||||
|
||||
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);
|
||||
this.subscriptions.push({
|
||||
subscription: sub,
|
||||
tier: response.tier || null,
|
||||
features: response.features || [],
|
||||
platform_id: platform.id,
|
||||
platform_name: platform.name,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status !== 404) {
|
||||
merchantDetailLog.warn(`Failed to load subscription for platform ${platform.name}:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
merchantDetailLog.info('Subscriptions loaded:', {
|
||||
count: this.subscriptions.length,
|
||||
platforms: this.subscriptions.map(e => e.platform_name)
|
||||
});
|
||||
},
|
||||
|
||||
// Load available subscription tiers
|
||||
async loadTiers() {
|
||||
if (this.tiers.length > 0) return;
|
||||
// Load available subscription tiers for a platform
|
||||
async loadTiers(platformId) {
|
||||
if (this.tiers.length > 0 && this.tiersForPlatformId === platformId) return;
|
||||
|
||||
try {
|
||||
const response = await apiClient.get('/admin/subscriptions/tiers');
|
||||
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);
|
||||
@@ -164,23 +158,32 @@ function adminMerchantDetail() {
|
||||
|
||||
// Open create subscription modal
|
||||
async openCreateSubscriptionModal() {
|
||||
await this.loadTiers();
|
||||
this.createForm = { tier_code: 'essential', status: 'trial', trial_days: 14, is_annual: false };
|
||||
const firstPlatformId = this.platforms.length > 0 ? this.platforms[0].id : null;
|
||||
this.createForm = { platform_id: firstPlatformId, tier_code: 'essential', status: 'trial', trial_days: 14, is_annual: false };
|
||||
await this.loadTiers(firstPlatformId);
|
||||
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.platformId) return;
|
||||
if (!this.merchantId || !this.createForm.platform_id) return;
|
||||
|
||||
this.creatingSubscription = true;
|
||||
merchantDetailLog.info('Creating subscription for merchant:', this.merchantId);
|
||||
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/${this.platformId}`;
|
||||
const url = `/admin/subscriptions/merchants/${this.merchantId}/platforms/${platformId}`;
|
||||
const payload = {
|
||||
merchant_id: parseInt(this.merchantId),
|
||||
platform_id: this.platformId,
|
||||
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,
|
||||
@@ -195,7 +198,7 @@ function adminMerchantDetail() {
|
||||
Utils.showToast('Subscription created successfully', 'success');
|
||||
merchantDetailLog.info('Subscription created');
|
||||
|
||||
await this.loadSubscription();
|
||||
await this.loadSubscriptions();
|
||||
|
||||
} catch (error) {
|
||||
window.LogConfig.logError(error, 'Create Subscription');
|
||||
@@ -276,9 +279,7 @@ function adminMerchantDetail() {
|
||||
async refresh() {
|
||||
merchantDetailLog.info('=== MERCHANT REFRESH TRIGGERED ===');
|
||||
await this.loadMerchant();
|
||||
if (this.platformId) {
|
||||
await this.loadSubscription();
|
||||
}
|
||||
await this.loadSubscriptions();
|
||||
Utils.showToast(I18n.t('tenancy.messages.merchant_details_refreshed'), 'success');
|
||||
merchantDetailLog.info('=== MERCHANT REFRESH COMPLETE ===');
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@ function adminStoreDetail() {
|
||||
// Store detail page specific state
|
||||
currentPage: 'store-detail',
|
||||
store: null,
|
||||
subscription: null,
|
||||
subscriptionTier: null,
|
||||
usageMetrics: [],
|
||||
subscriptions: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
storeCode: null,
|
||||
@@ -44,7 +42,7 @@ function adminStoreDetail() {
|
||||
await this.loadStore();
|
||||
// Load subscription after store is loaded
|
||||
if (this.store?.id) {
|
||||
await this.loadSubscription();
|
||||
await this.loadSubscriptions();
|
||||
}
|
||||
} else {
|
||||
detailLog.error('No store code in URL');
|
||||
@@ -102,14 +100,14 @@ function adminStoreDetail() {
|
||||
return formatted;
|
||||
},
|
||||
|
||||
// Load subscription data for this store via convenience endpoint
|
||||
async loadSubscription() {
|
||||
// Load subscriptions data for this store via convenience endpoint
|
||||
async loadSubscriptions() {
|
||||
if (!this.store?.id) {
|
||||
detailLog.warn('Cannot load subscription: no store ID');
|
||||
detailLog.warn('Cannot load subscriptions: no store ID');
|
||||
return;
|
||||
}
|
||||
|
||||
detailLog.info('Loading subscription for store:', this.store.id);
|
||||
detailLog.info('Loading subscriptions for store:', this.store.id);
|
||||
|
||||
try {
|
||||
const url = `/admin/subscriptions/store/${this.store.id}`;
|
||||
@@ -118,24 +116,20 @@ function adminStoreDetail() {
|
||||
const response = await apiClient.get(url);
|
||||
window.LogConfig.logApiCall('GET', url, response, 'response');
|
||||
|
||||
this.subscription = response.subscription;
|
||||
this.subscriptionTier = response.tier;
|
||||
this.usageMetrics = response.features || [];
|
||||
this.subscriptions = response.subscriptions || [];
|
||||
|
||||
detailLog.info('Subscription loaded:', {
|
||||
tier: this.subscription?.tier,
|
||||
status: this.subscription?.status,
|
||||
features_count: this.usageMetrics.length
|
||||
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 subscription found for store');
|
||||
this.subscription = null;
|
||||
this.usageMetrics = [];
|
||||
detailLog.info('No subscriptions found for store');
|
||||
this.subscriptions = [];
|
||||
} else {
|
||||
detailLog.warn('Failed to load subscription:', error.message);
|
||||
detailLog.warn('Failed to load subscriptions:', error.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user