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:
@@ -9,9 +9,7 @@ import api from '@/api/http'
|
|||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const adminSmsEnabled = ref(true)
|
const adminSmsEnabled = ref(true)
|
||||||
const pluginStoreEnabled = ref(false)
|
|
||||||
const smsToggleLoading = ref(false)
|
const smsToggleLoading = ref(false)
|
||||||
const storeToggleLoading = ref(false)
|
|
||||||
const canSettingsWrite = computed(() => auth.can('settings:write'))
|
const canSettingsWrite = computed(() => auth.can('settings:write'))
|
||||||
|
|
||||||
function goHome() {
|
function goHome() {
|
||||||
@@ -28,7 +26,6 @@ async function loadSmsToggle() {
|
|||||||
try {
|
try {
|
||||||
const { data } = await api.get('/settings/admin-sms')
|
const { data } = await api.get('/settings/admin-sms')
|
||||||
adminSmsEnabled.value = data.adminSmsEnabled !== false
|
adminSmsEnabled.value = data.adminSmsEnabled !== false
|
||||||
pluginStoreEnabled.value = data.pluginStoreEnabled === true
|
|
||||||
} catch {
|
} catch {
|
||||||
// keep default on; non-blocking for layout
|
// 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)
|
onMounted(loadSmsToggle)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -101,20 +79,6 @@ onMounted(loadSmsToggle)
|
|||||||
@change="onSmsToggle"
|
@change="onSmsToggle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<button type="button" class="icon-btn" title="داشبورد" @click="goHome">
|
||||||
<HomeOutlined />
|
<HomeOutlined />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useRouter } from 'vue-router'
|
|||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { ArrowRightOutlined, AppstoreAddOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
import { ArrowRightOutlined, AppstoreAddOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||||
import api from '@/api/http'
|
import api from '@/api/http'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
type ExpiryLevel = 'healthy' | 'warning' | 'critical' | 'unknown' | 'none'
|
type ExpiryLevel = 'healthy' | 'warning' | 'critical' | 'unknown' | 'none'
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ type TenantRow = {
|
|||||||
domain: string
|
domain: string
|
||||||
slug?: string
|
slug?: string
|
||||||
pluginCount: number
|
pluginCount: number
|
||||||
|
pluginStoreEnabled?: boolean
|
||||||
summary: {
|
summary: {
|
||||||
expiryLevel: ExpiryLevel
|
expiryLevel: ExpiryLevel
|
||||||
expiryLabel: string
|
expiryLabel: string
|
||||||
@@ -32,6 +34,9 @@ type RequestRow = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const canWrite = computed(() => auth.can('plugins:write'))
|
||||||
|
const storeBusyId = ref<string | null>(null)
|
||||||
const loadingList = ref(false)
|
const loadingList = ref(false)
|
||||||
const tenants = ref<TenantRow[]>([])
|
const tenants = ref<TenantRow[]>([])
|
||||||
const q = ref('')
|
const q = ref('')
|
||||||
@@ -72,6 +77,27 @@ function openTenant(id: string) {
|
|||||||
router.push({ name: 'plugin-tenant', params: { tenantId: id } })
|
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() {
|
async function loadTenants() {
|
||||||
loadingList.value = true
|
loadingList.value = true
|
||||||
try {
|
try {
|
||||||
@@ -306,6 +332,21 @@ onMounted(() => {
|
|||||||
:class="toneClass(t.summary?.expiryLevel)"
|
:class="toneClass(t.summary?.expiryLevel)"
|
||||||
@click="openTenant(t.id)"
|
@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>
|
<strong>{{ t.nameFa }}</strong>
|
||||||
<span>{{ t.summary?.expiryLabel }}</span>
|
<span>{{ t.summary?.expiryLabel }}</span>
|
||||||
<small>{{ t.pluginCount }} افزونه</small>
|
<small>{{ t.pluginCount }} افزونه</small>
|
||||||
@@ -409,7 +450,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.tenant-grid {
|
.tenant-grid {
|
||||||
display: 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;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,6 +467,28 @@ onMounted(() => {
|
|||||||
color: #17324d;
|
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 strong { font-size: 0.95rem; }
|
||||||
.tenant-card span,
|
.tenant-card span,
|
||||||
.tenant-card small { opacity: 0.92; font-size: 0.82rem; }
|
.tenant-card small { opacity: 0.92; font-size: 0.82rem; }
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ const loading = ref(false)
|
|||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const renewing = ref(false)
|
const renewing = ref(false)
|
||||||
const creating = ref(false)
|
const creating = ref(false)
|
||||||
|
const storeToggleLoading = ref(false)
|
||||||
|
const pluginStoreEnabled = computed(() => tenant.value?.pluginStoreEnabled === true)
|
||||||
const selectedId = ref<string | null>(null)
|
const selectedId = ref<string | null>(null)
|
||||||
const showCreate = ref(false)
|
const showCreate = ref(false)
|
||||||
|
|
||||||
@@ -52,6 +54,7 @@ const tenant = ref<{
|
|||||||
slug: string
|
slug: string
|
||||||
nameFa: string
|
nameFa: string
|
||||||
domain: string
|
domain: string
|
||||||
|
pluginStoreEnabled?: boolean
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const plugins = ref<PluginItem[]>([])
|
const plugins = ref<PluginItem[]>([])
|
||||||
const planOptions = ref<{ months: number; label: string }[]>([])
|
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)
|
watch(() => route.params.tenantId, loadDetail)
|
||||||
onMounted(loadDetail)
|
onMounted(loadDetail)
|
||||||
</script>
|
</script>
|
||||||
@@ -296,6 +320,20 @@ onMounted(loadDetail)
|
|||||||
{{ tenant?.product === 'hamkar' ? 'همکار' : 'خانگی' }} · {{ tenant?.domain }}
|
{{ tenant?.product === 'hamkar' ? 'همکار' : 'خانگی' }} · {{ tenant?.domain }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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">
|
<a-button v-if="canWrite" type="primary" @click="showCreate = true">
|
||||||
<PlusOutlined /> افزونه جدید
|
<PlusOutlined /> افزونه جدید
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -648,4 +686,24 @@ onMounted(loadDetail)
|
|||||||
color: #64748b;
|
color: #64748b;
|
||||||
font-size: 0.85rem;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user