fix(plugins): move plugin store toggle onto each khanegi tenant

Remove the global header switch and let SuperAdmin enable the store per project from the plugins list and tenant page.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-07 19:51:12 +03:30
co-authored by Cursor
parent 7eacb513cd
commit 8d0dfba60f
3 changed files with 122 additions and 37 deletions
-36
View File
@@ -9,9 +9,7 @@ import api from '@/api/http'
const auth = useAuthStore()
const router = useRouter()
const adminSmsEnabled = ref(true)
const pluginStoreEnabled = ref(false)
const smsToggleLoading = ref(false)
const storeToggleLoading = ref(false)
const canSettingsWrite = computed(() => auth.can('settings:write'))
function goHome() {
@@ -28,7 +26,6 @@ async function loadSmsToggle() {
try {
const { data } = await api.get('/settings/admin-sms')
adminSmsEnabled.value = data.adminSmsEnabled !== false
pluginStoreEnabled.value = data.pluginStoreEnabled === true
} catch {
// keep default on; non-blocking for layout
}
@@ -53,25 +50,6 @@ async function onSmsToggle(checked: boolean) {
}
}
async function onStoreToggle(checked: boolean) {
if (!canSettingsWrite.value) return
storeToggleLoading.value = true
const previous = pluginStoreEnabled.value
pluginStoreEnabled.value = checked
try {
const { data } = await api.patch('/settings/plugin-store', { enabled: checked })
pluginStoreEnabled.value = data.pluginStoreEnabled === true
message.success(
pluginStoreEnabled.value ? 'فروشگاه افزونه روشن شد' : 'فروشگاه افزونه خاموش شد',
)
} catch (e: any) {
pluginStoreEnabled.value = previous
message.error(e?.response?.data?.message || 'تغییر فروشگاه افزونه ناموفق')
} finally {
storeToggleLoading.value = false
}
}
onMounted(loadSmsToggle)
</script>
@@ -101,20 +79,6 @@ onMounted(loadSmsToggle)
@change="onSmsToggle"
/>
</div>
<div
v-if="canSettingsWrite"
class="sms-toggle"
:class="{ off: !pluginStoreEnabled }"
>
<span class="sms-toggle-label">فروشگاه افزونه</span>
<a-switch
:checked="pluginStoreEnabled"
:loading="storeToggleLoading"
checked-children="روشن"
un-checked-children="خاموش"
@change="onStoreToggle"
/>
</div>
<button type="button" class="icon-btn" title="داشبورد" @click="goHome">
<HomeOutlined />
</button>
+64 -1
View File
@@ -4,6 +4,7 @@ import { useRouter } from 'vue-router'
import { message } from 'ant-design-vue'
import { ArrowRightOutlined, AppstoreAddOutlined, SearchOutlined } from '@ant-design/icons-vue'
import api from '@/api/http'
import { useAuthStore } from '@/stores/auth'
type ExpiryLevel = 'healthy' | 'warning' | 'critical' | 'unknown' | 'none'
@@ -14,6 +15,7 @@ type TenantRow = {
domain: string
slug?: string
pluginCount: number
pluginStoreEnabled?: boolean
summary: {
expiryLevel: ExpiryLevel
expiryLabel: string
@@ -32,6 +34,9 @@ type RequestRow = {
}
const router = useRouter()
const auth = useAuthStore()
const canWrite = computed(() => auth.can('plugins:write'))
const storeBusyId = ref<string | null>(null)
const loadingList = ref(false)
const tenants = ref<TenantRow[]>([])
const q = ref('')
@@ -72,6 +77,27 @@ function openTenant(id: string) {
router.push({ name: 'plugin-tenant', params: { tenantId: id } })
}
async function onStoreToggle(tenant: TenantRow, checked: boolean) {
if (!canWrite.value) return
storeBusyId.value = tenant.id
const previous = tenant.pluginStoreEnabled === true
tenant.pluginStoreEnabled = checked
try {
const { data } = await api.patch(`/plugins/tenants/${tenant.id}/store-enabled`, {
enabled: checked,
})
tenant.pluginStoreEnabled = data.pluginStoreEnabled === true
message.success(
tenant.pluginStoreEnabled ? 'فروشگاه افزونه روشن شد' : 'فروشگاه افزونه خاموش شد',
)
} catch (e: any) {
tenant.pluginStoreEnabled = previous
message.error(e?.response?.data?.message || 'تغییر فروشگاه افزونه ناموفق')
} finally {
storeBusyId.value = null
}
}
async function loadTenants() {
loadingList.value = true
try {
@@ -306,6 +332,21 @@ onMounted(() => {
:class="toneClass(t.summary?.expiryLevel)"
@click="openTenant(t.id)"
>
<div
v-if="canWrite"
class="sms-toggle"
:class="{ off: !t.pluginStoreEnabled }"
@click.stop
>
<span class="sms-toggle-label">فروشگاه افزونه</span>
<a-switch
:checked="t.pluginStoreEnabled === true"
:loading="storeBusyId === t.id"
checked-children="روشن"
un-checked-children="خاموش"
@change="(v: boolean) => onStoreToggle(t, v)"
/>
</div>
<strong>{{ t.nameFa }}</strong>
<span>{{ t.summary?.expiryLabel }}</span>
<small>{{ t.pluginCount }} افزونه</small>
@@ -409,7 +450,7 @@ onMounted(() => {
.tenant-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 160px), 1fr));
grid-template-columns: repeat(auto-fill, minmax(min(100%, 210px), 1fr));
gap: 10px;
}
@@ -426,6 +467,28 @@ onMounted(() => {
color: #17324d;
}
.sms-toggle {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 6px 10px;
border-radius: 999px;
background: #e8f8f1;
border: 1px solid #b7ebd4;
margin-bottom: 6px;
}
.sms-toggle.off {
background: #fff1f0;
border-color: #ffccc7;
}
.sms-toggle-label {
font-size: 0.75rem;
font-weight: 700;
color: #334155;
white-space: nowrap;
}
.tenant-card strong { font-size: 0.95rem; }
.tenant-card span,
.tenant-card small { opacity: 0.92; font-size: 0.82rem; }
+58
View File
@@ -43,6 +43,8 @@ const loading = ref(false)
const saving = ref(false)
const renewing = ref(false)
const creating = ref(false)
const storeToggleLoading = ref(false)
const pluginStoreEnabled = computed(() => tenant.value?.pluginStoreEnabled === true)
const selectedId = ref<string | null>(null)
const showCreate = ref(false)
@@ -52,6 +54,7 @@ const tenant = ref<{
slug: string
nameFa: string
domain: string
pluginStoreEnabled?: boolean
} | null>(null)
const plugins = ref<PluginItem[]>([])
const planOptions = ref<{ months: number; label: string }[]>([])
@@ -280,6 +283,27 @@ function confirmDelete() {
})
}
async function onStoreToggle(checked: boolean) {
if (!canWrite.value || !tenantId.value || !isKhanegi.value) return
storeToggleLoading.value = true
const previous = tenant.value?.pluginStoreEnabled === true
if (tenant.value) tenant.value.pluginStoreEnabled = checked
try {
const { data } = await api.patch(`/plugins/tenants/${tenantId.value}/store-enabled`, {
enabled: checked,
})
if (tenant.value) tenant.value.pluginStoreEnabled = data.pluginStoreEnabled === true
message.success(
pluginStoreEnabled.value ? 'فروشگاه افزونه روشن شد' : 'فروشگاه افزونه خاموش شد',
)
} catch (e: any) {
if (tenant.value) tenant.value.pluginStoreEnabled = previous
message.error(e?.response?.data?.message || 'تغییر فروشگاه افزونه ناموفق')
} finally {
storeToggleLoading.value = false
}
}
watch(() => route.params.tenantId, loadDetail)
onMounted(loadDetail)
</script>
@@ -296,6 +320,20 @@ onMounted(loadDetail)
{{ tenant?.product === 'hamkar' ? 'همکار' : 'خانگی' }} · {{ tenant?.domain }}
</p>
</div>
<div
v-if="isKhanegi && canWrite"
class="sms-toggle"
:class="{ off: !pluginStoreEnabled }"
>
<span class="sms-toggle-label">فروشگاه افزونه</span>
<a-switch
:checked="pluginStoreEnabled"
:loading="storeToggleLoading"
checked-children="روشن"
un-checked-children="خاموش"
@change="onStoreToggle"
/>
</div>
<a-button v-if="canWrite" type="primary" @click="showCreate = true">
<PlusOutlined /> افزونه جدید
</a-button>
@@ -648,4 +686,24 @@ onMounted(loadDetail)
color: #64748b;
font-size: 0.85rem;
}
.sms-toggle {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-radius: 999px;
background: #e8f8f1;
border: 1px solid #b7ebd4;
}
.sms-toggle.off {
background: #fff1f0;
border-color: #ffccc7;
}
.sms-toggle-label {
font-size: 0.8rem;
font-weight: 700;
color: #334155;
white-space: nowrap;
}
</style>