Add module management system for enabling/disabling features
Stores can enable/disable feature modules from Settings. When disabled, nav links are hidden and API routes return 403. Designed as the foundation for future license-based gating (licensed + enabled flags). Core modules (Accounts, Members, Users, Roles, Settings) are always on. - module_config table with slug, name, description, licensed, enabled - In-memory cache for fast per-request module checks - requireModule middleware wraps route groups in main.ts - Settings page Modules card with toggle switches - Sidebar hides nav links for disabled modules - Default modules seeded: inventory, pos, repairs, rentals, lessons, files, vault, email, reports
This commit is contained in:
@@ -5,6 +5,7 @@ import { useEffect, useState } from 'react'
|
||||
import { api } from '@/lib/api-client'
|
||||
import { useAuthStore } from '@/stores/auth.store'
|
||||
import { myPermissionsOptions } from '@/api/rbac'
|
||||
import { moduleListOptions } from '@/api/modules'
|
||||
import { Avatar } from '@/components/shared/avatar-upload'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Users, UserRound, HelpCircle, Shield, UserCog, LogOut, User, Wrench, Package, ClipboardList, FolderOpen, KeyRound, Settings } from 'lucide-react'
|
||||
@@ -90,6 +91,17 @@ function AuthenticatedLayout() {
|
||||
enabled: !!useAuthStore.getState().token,
|
||||
})
|
||||
|
||||
// Fetch enabled modules
|
||||
const { data: modulesData } = useQuery({
|
||||
...moduleListOptions(),
|
||||
enabled: !!useAuthStore.getState().token,
|
||||
})
|
||||
|
||||
const enabledModules = new Set(
|
||||
(modulesData?.data ?? []).filter((m) => m.enabled && m.licensed).map((m) => m.slug),
|
||||
)
|
||||
const isModuleEnabled = (slug: string) => enabledModules.has(slug)
|
||||
|
||||
useEffect(() => {
|
||||
if (permData?.permissions) {
|
||||
setPermissions(permData.permissions)
|
||||
@@ -124,7 +136,7 @@ function AuthenticatedLayout() {
|
||||
<NavLink to="/members" icon={<UserRound className="h-4 w-4" />} label="Members" />
|
||||
</>
|
||||
)}
|
||||
{canViewRepairs && (
|
||||
{isModuleEnabled('repairs') && canViewRepairs && (
|
||||
<>
|
||||
<NavLink to="/repairs" icon={<Wrench className="h-4 w-4" />} label="Repairs" />
|
||||
<NavLink to="/repair-batches" icon={<Package className="h-4 w-4" />} label="Repair Batches" />
|
||||
@@ -133,8 +145,12 @@ function AuthenticatedLayout() {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<NavLink to="/files" icon={<FolderOpen className="h-4 w-4" />} label="Files" />
|
||||
<NavLink to="/vault" icon={<KeyRound className="h-4 w-4" />} label="Vault" />
|
||||
{isModuleEnabled('files') && (
|
||||
<NavLink to="/files" icon={<FolderOpen className="h-4 w-4" />} label="Files" />
|
||||
)}
|
||||
{isModuleEnabled('vault') && (
|
||||
<NavLink to="/vault" icon={<KeyRound className="h-4 w-4" />} label="Vault" />
|
||||
)}
|
||||
{canViewUsers && (
|
||||
<div className="mt-4 mb-1 px-3">
|
||||
<span className="text-xs font-semibold text-sidebar-foreground/50 uppercase tracking-wide">Admin</span>
|
||||
|
||||
Reference in New Issue
Block a user