feat: allow suspended accounts to access billing (#15153)
Administrators of suspended accounts can now reach the billing page to settle payment and restore their account, instead of being fully locked out on the suspended screen. The suspended screen shows a subtle **Manage billing** link below the contact support button (admins on Cloud only). Agents remain restricted to the suspended screen. ## How to test 1. On Cloud, suspend an account from Super Admin. 2. Log in as an administrator of that account — you land on the suspended screen with a "Manage billing" link below contact support. 3. Click it — the billing settings page opens; subscription, Stripe portal, and top-ups all work. 4. Log in as an agent — no billing link, and navigating to `/settings/billing` manually redirects back to the suspended screen. ## What changed - Router guard allows `billing_settings_index` for administrators when the account is not active. - Suspended screen gets a billing link gated by admin role and Cloud installation. <img width="3024" height="1718" alt="CleanShot 2026-07-24 at 12 25 56@2x" src="https://github.com/user-attachments/assets/1905d070-5e67-4283-9c8d-6e9936bba9a3" /> --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -67,8 +67,14 @@ export const validateLoggedInRoutes = (to, user) => {
|
||||
return validateActiveAccountRoutes(to, user);
|
||||
}
|
||||
|
||||
// If the current account is not active, then redirect the user to the suspended screen
|
||||
if (to.name !== 'account_suspended') {
|
||||
// If the current account is not active, only the suspended screen is
|
||||
// reachable; administrators can also access billing to restore the account
|
||||
const userPermissions = getUserPermissions(user, to.params.accountId);
|
||||
const accessibleRoutes = userPermissions.includes('administrator')
|
||||
? ['account_suspended', 'billing_settings_index']
|
||||
: ['account_suspended'];
|
||||
|
||||
if (!accessibleRoutes.includes(to.name)) {
|
||||
return `accounts/${to.params.accountId}/suspended`;
|
||||
}
|
||||
|
||||
|
||||
@@ -264,7 +264,8 @@
|
||||
"EMAIL_VERIFICATION_SENT": "Verification email has been sent. Please check your inbox.",
|
||||
"ACCOUNT_SUSPENDED": {
|
||||
"TITLE": "Account Suspended",
|
||||
"MESSAGE": "Your account has been suspended due to activity that may violate our policies. If you believe this is a mistake, please contact our support team."
|
||||
"MESSAGE": "Your account has been suspended due to activity that may violate our policies. If you believe this is a mistake, please contact our support team.",
|
||||
"MANAGE_BILLING": "Manage billing"
|
||||
},
|
||||
"NO_ACCOUNTS": {
|
||||
"TITLE": "No account found",
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
<script setup>
|
||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import { onMounted } from 'vue';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
|
||||
const { isAdmin } = useAdmin();
|
||||
const isOnChatwootCloud = useMapGetter('globalConfig/isOnChatwootCloud');
|
||||
|
||||
const showBillingLink = computed(
|
||||
() => isAdmin.value && isOnChatwootCloud.value
|
||||
);
|
||||
|
||||
const toggleSupportWidgetVisibility = () => {
|
||||
if (window.$chatwoot) {
|
||||
@@ -30,15 +39,23 @@ onMounted(() => {
|
||||
<template>
|
||||
<div class="items-center bg-n-slate-2 flex justify-center h-full w-full">
|
||||
<EmptyState
|
||||
class="max-w-lg"
|
||||
:title="$t('APP_GLOBAL.ACCOUNT_SUSPENDED.TITLE')"
|
||||
:message="$t('APP_GLOBAL.ACCOUNT_SUSPENDED.MESSAGE')"
|
||||
>
|
||||
<div class="flex justify-center">
|
||||
<div class="flex flex-col items-center gap-3 mt-4">
|
||||
<NextButton
|
||||
icon="i-lucide-life-buoy"
|
||||
:label="$t('SIDEBAR_ITEMS.CONTACT_SUPPORT')"
|
||||
@click="toggleSupportWidget"
|
||||
/>
|
||||
<router-link
|
||||
v-if="showBillingLink"
|
||||
:to="{ name: 'billing_settings_index' }"
|
||||
class="text-sm text-n-slate-11 hover:text-n-slate-12 hover:underline"
|
||||
>
|
||||
{{ $t('APP_GLOBAL.ACCOUNT_SUSPENDED.MANAGE_BILLING') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</EmptyState>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user