From 8d0dfba60f8fb948e25a2bc3be052523f3e7aa8a Mon Sep 17 00:00:00 2001
From: adonis
Date: Mon, 7 Sep 2026 19:51:12 +0330
Subject: [PATCH] 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
---
src/layouts/AppLayout.vue | 36 -------------------
src/pages/PluginPanelPage.vue | 65 +++++++++++++++++++++++++++++++++-
src/pages/PluginTenantPage.vue | 58 ++++++++++++++++++++++++++++++
3 files changed, 122 insertions(+), 37 deletions(-)
diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue
index ef40781..2484b3e 100644
--- a/src/layouts/AppLayout.vue
+++ b/src/layouts/AppLayout.vue
@@ -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)
@@ -101,20 +79,6 @@ onMounted(loadSmsToggle)
@change="onSmsToggle"
/>
-
diff --git a/src/pages/PluginPanelPage.vue b/src/pages/PluginPanelPage.vue
index 647706a..17051ca 100644
--- a/src/pages/PluginPanelPage.vue
+++ b/src/pages/PluginPanelPage.vue
@@ -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(null)
const loadingList = ref(false)
const tenants = ref([])
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)"
>
+
+
فروشگاه افزونه
+
onStoreToggle(t, v)"
+ />
+
{{ t.nameFa }}
{{ t.summary?.expiryLabel }}
{{ t.pluginCount }} افزونه
@@ -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; }
diff --git a/src/pages/PluginTenantPage.vue b/src/pages/PluginTenantPage.vue
index 6550c33..89b0466 100644
--- a/src/pages/PluginTenantPage.vue
+++ b/src/pages/PluginTenantPage.vue
@@ -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(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([])
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)
@@ -296,6 +320,20 @@ onMounted(loadDetail)
{{ tenant?.product === 'hamkar' ? 'همکار' : 'خانگی' }} · {{ tenant?.domain }}
+
افزونه جدید
@@ -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;
+}