feat(adm): split chat and billing settings and add a master login

Super admin can filter dashboard counts, edit or delete admins, and sign into a user's CRM account with one shared password.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-02 19:13:57 +03:30
co-authored by Cursor
parent acb5d51f24
commit c273dd6c58
16 changed files with 1031 additions and 253 deletions
+3 -43
View File
@@ -1,20 +1,10 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import axios from 'axios';
import type {
ApplyBillingPlanRequest,
BillingPlanCode,
PlatformUserDto,
UserRole,
} from '@mento/shared';
import {
BILLING_PLAN_LABELS,
USER_ROLE_LABELS,
USER_ROLES,
} from '@mento/shared';
import type { PlatformUserDto, UserRole } from '@mento/shared';
import { USER_ROLE_LABELS, USER_ROLES } from '@mento/shared';
import { platformApi } from '@/lib/platform-api';
import { formatJalaliNumeric, toFaDigits } from '@/lib/jalali';
import AdmBillingPanel from './AdmBillingPanel.vue';
const query = ref('');
const ownersOnly = ref(false);
@@ -135,25 +125,6 @@ async function setMembershipRole(userId: string, membershipId: string, role: Use
}
}
async function applyPlan(userId: string, planCode: BillingPlanCode) {
busyId.value = `plan-${userId}-${planCode}`;
error.value = '';
success.value = '';
try {
const body: ApplyBillingPlanRequest = { planCode };
const { data } = await platformApi.post<PlatformUserDto>(
`/platform/users/${userId}/billing/apply-plan`,
body,
);
items.value = items.value.map((row) => (row.id === userId ? data : row));
success.value = `پلن «${BILLING_PLAN_LABELS[planCode]}» اعمال شد`;
} catch (e) {
error.value = apiError(e, 'اعمال پلن ناموفق بود');
} finally {
busyId.value = '';
}
}
function setOwnersOnly(value: boolean) {
if (ownersOnly.value === value) return;
ownersOnly.value = value;
@@ -168,7 +139,7 @@ onMounted(() => {
<template>
<div class="page">
<header class="head">
<p class="lead">اشتراک مالک، نقش عضویت و ریست رمز.</p>
<p class="lead">نقش عضویت و ریست رمز. اشتراک از منوی اشتراک تنظیم می‌شود.</p>
<div class="head-actions">
<div class="seg" role="group" aria-label="فیلتر مالکان">
<button
@@ -264,17 +235,6 @@ onMounted(() => {
</form>
<div v-if="openId === user.id" class="detail">
<AdmBillingPanel
v-if="user.isBillingOwner"
:user-id="user.id"
:billing="user.billing"
:busy-id="busyId"
@apply="applyPlan(user.id, $event)"
/>
<p v-else class="muted billing-skip">
این کاربر عضو زیرمجموعه است و اشتراک جدا ندارد؛ اشتراک روی مالک محیط است.
</p>
<p v-if="!user.memberships.length" class="muted">عضو هیچ محیطی نیست.</p>
<ul v-else>
<li v-for="mem in user.memberships" :key="mem.membershipId">