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:
2026-02-10 19:17:51 +01:00
parent 0984ff7d17
commit 0b37274140
14 changed files with 414 additions and 326 deletions

View File

@@ -5,12 +5,12 @@
{% from 'shared/macros/tables.html' import table_wrapper, table_header_custom, th_sortable %}
{% from 'shared/macros/pagination.html' import pagination %}
{% block title %}Store Subscriptions{% endblock %}
{% block title %}Merchant Subscriptions{% endblock %}
{% block alpine_data %}adminSubscriptions(){% endblock %}
{% block content %}
{{ page_header_refresh('Store Subscriptions') }}
{{ page_header_refresh('Merchant Subscriptions') }}
{{ alert_dynamic(type='success', title='Success', message_var='successMessage', show_condition='successMessage') }}
@@ -94,7 +94,7 @@
type="text"
x-model="filters.search"
@input.debounce.300ms="loadSubscriptions()"
placeholder="Search store name..."
placeholder="Search merchant name..."
class="w-full px-3 py-2 border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-white"
>
</div>
@@ -140,7 +140,8 @@
{% call table_wrapper() %}
<table class="w-full whitespace-nowrap">
{% call table_header_custom() %}
{{ th_sortable('store_name', 'Store', 'sortBy', 'sortOrder') }}
{{ th_sortable('merchant_name', 'Merchant', 'sortBy', 'sortOrder') }}
<th class="px-4 py-3">Platform</th>
{{ th_sortable('tier', 'Tier', 'sortBy', 'sortOrder') }}
{{ th_sortable('status', 'Status', 'sortBy', 'sortOrder') }}
<th class="px-4 py-3 text-center">Features</th>
@@ -150,7 +151,7 @@
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
<template x-if="loading">
<tr>
<td colspan="6" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
<td colspan="7" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
<span x-html="$icon('refresh', 'inline w-6 h-6 animate-spin mr-2')"></span>
Loading subscriptions...
</td>
@@ -158,7 +159,7 @@
</template>
<template x-if="!loading && subscriptions.length === 0">
<tr>
<td colspan="6" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
<td colspan="7" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
No subscriptions found.
</td>
</tr>
@@ -168,11 +169,13 @@
<td class="px-4 py-3">
<div class="flex items-center">
<div>
<p class="font-semibold text-gray-900 dark:text-gray-100" x-text="sub.store_name"></p>
<p class="text-xs text-gray-500" x-text="sub.store_code"></p>
<p class="font-semibold text-gray-900 dark:text-gray-100" x-text="sub.merchant_name"></p>
</div>
</div>
</td>
<td class="px-4 py-3">
<span class="text-sm text-gray-600 dark:text-gray-400" x-text="sub.platform_name || '-'"></span>
</td>
<td class="px-4 py-3">
<span class="px-2 py-1 text-xs font-medium rounded-full"
:class="{
@@ -204,7 +207,7 @@
<button @click="openEditModal(sub)" class="p-2 text-gray-500 hover:text-purple-600 dark:hover:text-purple-400" title="Edit">
<span x-html="$icon('pencil', 'w-4 h-4')"></span>
</button>
<a :href="'/admin/stores/' + sub.store_code" class="p-2 text-gray-500 hover:text-blue-600 dark:hover:text-blue-400" title="View Store">
<a :href="'/admin/merchants/' + sub.merchant_id" class="p-2 text-gray-500 hover:text-blue-600 dark:hover:text-blue-400" title="View Merchant">
<span x-html="$icon('external-link', 'w-4 h-4')"></span>
</a>
</div>
@@ -224,20 +227,21 @@
<div x-show="showModal" x-cloak class="fixed inset-0 z-50 flex items-center justify-center overflow-auto bg-black bg-opacity-50">
<div class="relative w-full max-w-lg p-6 mx-4 bg-white rounded-lg shadow-xl dark:bg-gray-800" @click.away="closeModal()">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Edit Subscription</h3>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4" x-text="'Store: ' + (editingSub?.store_name || '')"></p>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-1" x-text="'Merchant: ' + (editingSub?.merchant_name || '')"></p>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4" x-show="editingSub?.platform_name" x-text="'Platform: ' + (editingSub?.platform_name || '')"></p>
<div class="space-y-4">
<!-- Tier -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Tier</label>
<select
x-model="formData.tier"
x-model="formData.tier_code"
class="w-full px-3 py-2 border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-white"
:disabled="loadingTiers"
>
<option value="essential">Essential</option>
<option value="professional">Professional</option>
<option value="business">Business</option>
<option value="enterprise">Enterprise</option>
<template x-for="t in editTiers" :key="t.code">
<option :value="t.code" x-text="t.name"></option>
</template>
</select>
</div>