Gate Roll Call behind the opt-in business module.

Super Admin can enable it per business; nav and pages stay hidden when off.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-07 14:15:46 +03:30
co-authored by Cursor
parent c4e846564e
commit b25f3cbea0
9 changed files with 34 additions and 9 deletions
+1 -1
View File
@@ -170,7 +170,7 @@ export function Sidebar({ open = false }: SidebarProps) {
{ labelKey: 'nav.store.settings', to: '/store/settings' },
],
},
...(canAccessRollCall(user, getActiveBusinessId())
...(canAccessRollCall(user, getActiveBusinessId(), enabledModules)
? [
{
type: 'group' as const,
+3 -1
View File
@@ -28,6 +28,7 @@ import {
type RollCallSummary,
type RollCallsListResponse,
} from '../services/rollCallService'
import { useTenantBranding } from '../context/TenantBrandingContext'
import { canAccessRollCall } from '../utils/rollCallAccess'
import pageStyles from '../components/PageContent.module.css'
import styles from './RollCallPage.module.css'
@@ -102,8 +103,9 @@ export function RollCallPage() {
const { locale } = useLocale()
const { showToast } = useToast()
const { user } = useAuth()
const { enabledModules } = useTenantBranding()
const activeBusinessId = getActiveBusinessId()
const allowed = canAccessRollCall(user, activeBusinessId)
const allowed = canAccessRollCall(user, activeBusinessId, enabledModules)
const [data, setData] = useState<RollCallsListResponse | null>(null)
const [loading, setLoading] = useState(false)
+3 -1
View File
@@ -11,6 +11,7 @@ export const BUSINESS_DASHBOARD_MODULE_IDS = [
'workshops',
'finance',
'multilanguage_data',
'roll_call',
] as const
/** Optional customer-dashboard modules. Always-on: home, profile, addresses, orders, favorites. */
@@ -57,7 +58,8 @@ export const DEFAULT_ENABLED_BUSINESS_MODULES: BusinessModuleId[] =
(id) =>
id !== 'workshops' &&
id !== 'multilanguage_data' &&
id !== 'store_installments',
id !== 'store_installments' &&
id !== 'roll_call',
)
export const DEFAULT_HOME_CHARTS: [HomeChartId, HomeChartId] = [
+10
View File
@@ -1,4 +1,8 @@
import type { AuthUser } from '../types/auth'
import {
hasBusinessModule,
type BusinessModuleId,
} from './businessModules'
function membershipAllowsRollCall(membership: {
isOwner: boolean
@@ -12,10 +16,16 @@ function membershipAllowsRollCall(membership: {
return membership.permissions.includes('roll_calls.read')
}
/**
* Show Roll Call when the business module is enabled and the user is
* staff (owner / admin) for the active tenant.
*/
export function canAccessRollCall(
user: AuthUser | null | undefined,
activeBusinessId: string | null,
enabledModules?: readonly BusinessModuleId[] | null,
): boolean {
if (!hasBusinessModule(enabledModules, 'roll_call')) return false
if (!user) return false
if (user.isSuperAdmin || user.roles.includes('super_admin')) {
+1 -1
View File
@@ -32,7 +32,7 @@ export function Sidebar({ open = false }: SidebarProps) {
const displayName = brandName || fallbackBusinessName
const showMyProducts = hasBusinessModule(enabledModules, 'customer_products')
const activeBusinessId = getActiveBusinessId()
const showRollCall = canAccessRollCall(user, activeBusinessId)
const showRollCall = canAccessRollCall(user, activeBusinessId, enabledModules)
const navItems = [
{ icon: Home, label: t('nav.home'), to: '/' },
+3 -1
View File
@@ -27,6 +27,7 @@ import {
type RollCallSummary,
type RollCallsListResponse,
} from '../services/rollCallService'
import { useTenantBranding } from '../context/TenantBrandingContext'
import { canAccessRollCall } from '../utils/rollCallAccess'
import pageStyles from '../components/PageContent.module.css'
import styles from './RollCallPage.module.css'
@@ -101,8 +102,9 @@ export function RollCallPage() {
const { locale } = useLocale()
const { showToast } = useToast()
const { user } = useAuth()
const { enabledModules } = useTenantBranding()
const activeBusinessId = getActiveBusinessId()
const allowed = canAccessRollCall(user, activeBusinessId)
const allowed = canAccessRollCall(user, activeBusinessId, enabledModules)
const [data, setData] = useState<RollCallsListResponse | null>(null)
const [loading, setLoading] = useState(false)
@@ -11,6 +11,7 @@ export const BUSINESS_MODULE_IDS = [
'blog',
'warehouse',
'videos',
'roll_call',
...CUSTOMER_MODULE_IDS,
] as const
+8 -3
View File
@@ -1,4 +1,8 @@
import type { AuthUser } from '../types/auth'
import {
hasBusinessModule,
type BusinessModuleId,
} from './businessModules'
function membershipAllowsRollCall(membership: {
isOwner: boolean
@@ -19,14 +23,15 @@ function sameId(a: string | number | null | undefined, b: string | number | null
}
/**
* Show Roll Call in the customer dashboard when the user is staff
* (owner / admin) for the active tenant — same people who can open
* the business dashboard. No business module toggle is required.
* Show Roll Call when the business module is enabled and the user is
* staff (owner / admin) for the active tenant.
*/
export function canAccessRollCall(
user: AuthUser | null | undefined,
activeBusinessId: string | null,
enabledModules?: readonly BusinessModuleId[] | null,
): boolean {
if (!hasBusinessModule(enabledModules, 'roll_call')) return false
if (!user) return false
if (user.isSuperAdmin || user.roles.includes('super_admin')) {
@@ -11,6 +11,7 @@ export const BUSINESS_DASHBOARD_MODULE_IDS = [
'workshops',
'finance',
'multilanguage_data',
'roll_call',
] as const
/** Optional customer-dashboard modules. Always-on: home, profile, addresses, orders, favorites. */
@@ -42,6 +43,7 @@ export const BUSINESS_DASHBOARD_MODULE_LABELS: Record<
workshops: 'Workshops',
finance: 'Finance',
multilanguage_data: 'MultiLanguage Data',
roll_call: 'Roll Call',
}
export const CUSTOMER_MODULE_LABELS: Record<CustomerModuleId, string> = {
@@ -87,7 +89,8 @@ export const DEFAULT_ENABLED_BUSINESS_MODULES: BusinessModuleId[] =
(id) =>
id !== 'workshops' &&
id !== 'multilanguage_data' &&
id !== 'store_installments',
id !== 'store_installments' &&
id !== 'roll_call',
)
export const DEFAULT_HOME_CHARTS: [HomeChartId, HomeChartId] = [