feat(billing): start demos on the core plan and apply tariff levels

New signups get the core module set with the 7-day demo. Superadmin can apply core, team, organization, or dedicated and the owner's environments take that level's access.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-10 14:08:14 +03:30
co-authored by Cursor
parent 5b26b9156d
commit 932dc3f1c1
8 changed files with 212 additions and 5 deletions
@@ -6,8 +6,9 @@ import type {
BillingPlanCode,
PlatformOrganizationDto,
PlatformOrgMemberDto,
ProductPlanCode,
} from '@mento/shared';
import { BILLING_PLAN_LABELS } from '@mento/shared';
import { BILLING_PLAN_LABELS, PRODUCT_PLAN_LABELS } from '@mento/shared';
import { platformApi } from '@/lib/platform-api';
import AdmBillingPanel from './AdmBillingPanel.vue';
@@ -58,6 +59,21 @@ async function loadOwners() {
}
}
async function applyProduct(userId: string, planCode: ProductPlanCode) {
busyId.value = `product-${userId}-${planCode}`;
error.value = '';
success.value = '';
try {
await platformApi.post(`/platform/organizations/${orgId.value}/product-plan`, { planCode });
await loadOwners();
success.value = `سطح «${PRODUCT_PLAN_LABELS[planCode]}» روی دسترسی‌ها اعمال شد`;
} catch (e) {
error.value = apiError(e, 'اعمال سطح امکانات ناموفق بود');
} finally {
busyId.value = '';
}
}
async function applyPlan(userId: string, planCode: BillingPlanCode) {
busyId.value = `plan-${userId}-${planCode}`;
error.value = '';
@@ -100,11 +116,12 @@ onMounted(() => {
<p v-else-if="orgId && !owners.length" class="muted">این محیط مالک فعالی ندارد.</p>
<AdmBillingPanel
v-for="owner in owners"
:key="`${owner.userId}-${owner.billing?.planCode ?? ''}-${owner.billing?.expiresAt ?? ''}`"
:key="`${owner.userId}-${owner.billing?.planCode ?? ''}-${owner.billing?.expiresAt ?? ''}-${owner.billing?.maxUsers ?? ''}-${owner.billing?.maxOrganizations ?? ''}`"
:user-id="owner.userId"
:billing="owner.billing"
:busy-id="busyId"
@apply="applyPlan(owner.userId, $event)"
@product="applyProduct(owner.userId, $event)"
/>
</div>
</div>