diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue
index 2484b3e..ef40781 100644
--- a/src/layouts/AppLayout.vue
+++ b/src/layouts/AppLayout.vue
@@ -9,7 +9,9 @@ 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() {
@@ -26,6 +28,7 @@ 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
}
@@ -50,6 +53,25 @@ 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)
@@ -79,6 +101,20 @@ onMounted(loadSmsToggle)
@change="onSmsToggle"
/>
+
diff --git a/src/pages/PluginCatalogPage.vue b/src/pages/PluginCatalogPage.vue
new file mode 100644
index 0000000..2c70f92
--- /dev/null
+++ b/src/pages/PluginCatalogPage.vue
@@ -0,0 +1,237 @@
+
+
+
+
+
+
+
+
+
+
قیمت فروشگاه خانگی
+
برای همهٔ نسخههای خانگی یکی است. مدت خاموش در ادمین دیده نمیشود.
+
+
+ تنظیم جدید
+
+
+
+
+
+
+
+
+ {{ item.titleFa }}
+ {{ item.settingKey }}
+
+
+
+ {{ item.description }}
+
+
+
{{ durationLabel[price.durationDays] }}
+
+
+
+
+
+
+ ذخیره
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/PluginPanelPage.vue b/src/pages/PluginPanelPage.vue
index c89da7e..647706a 100644
--- a/src/pages/PluginPanelPage.vue
+++ b/src/pages/PluginPanelPage.vue
@@ -20,11 +20,27 @@ type TenantRow = {
}
}
+type RequestRow = {
+ id: string
+ tenantName: string
+ domain: string
+ pluginTitle: string
+ durationDays: number
+ finalPriceRials: number
+ status: 'pending' | 'approved' | 'rejected' | 'cancelled'
+ createdAt: string
+}
+
const router = useRouter()
const loadingList = ref(false)
const tenants = ref([])
const q = ref('')
const toneFilter = ref<'healthy' | 'warning' | 'critical' | 'unknown' | null>(null)
+const mainTab = ref<'khanegi' | 'hamkar' | 'requests'>('khanegi')
+const requests = ref([])
+const loadingRequests = ref(false)
+const actingId = ref(null)
+const autoCut = ref(true)
const filteredTenants = computed(() => {
const term = q.value.trim().toLowerCase()
@@ -68,7 +84,64 @@ async function loadTenants() {
}
}
-onMounted(loadTenants)
+const statusLabel: Record = {
+ pending: 'در انتظار',
+ approved: 'تأیید شده',
+ rejected: 'رد شده',
+ cancelled: 'لغو شده',
+}
+
+async function loadRequests() {
+ loadingRequests.value = true
+ try {
+ const { data } = await api.get('/plugins/store/requests')
+ requests.value = data
+ } catch (e: any) {
+ message.error(e?.response?.data?.message || 'خطا در دریافت درخواستها')
+ } finally {
+ loadingRequests.value = false
+ }
+}
+
+async function approve(row: RequestRow) {
+ actingId.value = row.id
+ try {
+ await api.post(`/plugins/store/requests/${row.id}/approve`, {
+ autoCutOnExpiry: autoCut.value,
+ })
+ message.success('تأیید شد و سوییچ روشن شد')
+ await loadRequests()
+ await loadTenants()
+ } catch (e: any) {
+ const msg = e?.response?.data?.message
+ message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'تأیید ناموفق')
+ } finally {
+ actingId.value = null
+ }
+}
+
+async function reject(row: RequestRow) {
+ actingId.value = row.id
+ try {
+ await api.post(`/plugins/store/requests/${row.id}/reject`)
+ message.success('رد شد')
+ await loadRequests()
+ } catch (e: any) {
+ const msg = e?.response?.data?.message
+ message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'رد ناموفق')
+ } finally {
+ actingId.value = null
+ }
+}
+
+function setTab(tab: 'khanegi' | 'hamkar' | 'requests') {
+ mainTab.value = tab
+ if (tab === 'requests') void loadRequests()
+}
+
+onMounted(() => {
+ loadTenants()
+})
@@ -86,7 +159,13 @@ onMounted(loadTenants)
-