@mounaji_npm/saas-template
v0.4.2
Published
Complete SaaS app shell with module-registry nav, sidebar, topbar and page modules — install once, add pages incrementally
Maintainers
Readme
@mounaji_npm/saas-template
Complete SaaS app shell with sidebar, topnav, and module registry. Install once — add pages incrementally by registering module manifests.
This package is part of the app setup layer, not the CLI layer. It gives you the runtime shell and starter module manifests inside an existing app. Project scaffolding stays in @mounaji_npm/cli.
Install
npm install @mounaji_npm/tokens @mounaji_npm/core @mounaji_npm/saas-templateRecommended pairing:
@mounaji_npm/corefor providers, bootstrap, guards, capability registry, and gates@mounaji_npm/saas-templateforAppShell, sidebar/topnav, and module manifests@mounaji_npm/clionly when scaffolding a brand new project
Quick Start
Next.js App Router
// app/providers.jsx
'use client';
import { useState } from 'react';
import { usePathname } from 'next/navigation';
import Link from 'next/link';
import { AppShell } from '@mounaji_npm/saas-template';
import { CORE_MODULES } from '@mounaji_npm/saas-template/modules';
export function Providers({ children }) {
const pathname = usePathname();
const [dark, setDark] = useState(true);
return (
<AppShell
modules={CORE_MODULES}
activePath={pathname}
LinkComponent={Link}
isDark={dark}
onThemeToggle={() => setDark(d => !d)}
>
{children}
</AppShell>
);
}
// app/layout.js
import { Providers } from './providers';
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Vite + React
// src/App.jsx
import { useState } from 'react';
import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';
import { AppShell } from '@mounaji_npm/saas-template';
import { CORE_MODULES } from '@mounaji_npm/saas-template/modules';
import DashboardPage from './pages/DashboardPage';
import ChatPage from './pages/ChatPage';
function AppInner() {
const { pathname } = useLocation();
const [dark, setDark] = useState(true);
return (
<AppShell
modules={CORE_MODULES}
activePath={pathname}
isDark={dark}
onThemeToggle={() => setDark(d => !d)}
>
<Routes>
<Route path="/" element={<DashboardPage />} />
<Route path="/chat" element={<ChatPage />} />
</Routes>
</AppShell>
);
}
export default function App() {
return <BrowserRouter><AppInner /></BrowserRouter>;
}AppShell Props
| Prop | Type | Default | Description |
|---|---|---|---|
| modules | ModuleManifest[] | [] | Nav modules shown in the sidebar (main section) |
| bottomModules | ModuleManifest[] | [SETTINGS_MODULE, HELP_MODULE] | Nav items pinned to bottom |
| activePath | string | '/' | Current route — highlights the matching nav item |
| onNavigate | function | — | (path: string) => void — used when LinkComponent is not provided |
| LinkComponent | component | 'a' | Router link component (Link from Next.js or React Router) |
| logo | ReactNode | default M | Logo in the sidebar header |
| org | object | — | { name: string, logo?: string } — shown in TopNav breadcrumb |
| project | object | — | { name: string } — shown in TopNav breadcrumb |
| onOrgClick | function | — | Called when org name is clicked |
| onProjectClick | function | — | Called when project name is clicked |
| onNewProject | function | — | Called when "New Project" button is clicked |
| user | object | — | { name, email, avatar? } — shown in TopNav user menu |
| onLogout | function | — | Called from user menu logout button |
| isDark | boolean | true | Dark/light theme |
| onThemeToggle | function | — | Called by theme toggle button in TopNav |
| topNavLeft | ReactNode | — | Extra content in TopNav left slot |
| topNavCenter | ReactNode | — | Extra content in TopNav center slot |
| topNavRight | ReactNode | — | Extra content in TopNav right slot |
| tokens | object | — | Token overrides merged with DEFAULT_TOKENS |
| tokenPersist | boolean | true | Persist token overrides to localStorage |
| tokenStorageKey | string | 'mn_tokens' | localStorage key for tokens |
| i18n | object | — | See i18n integration |
Module Manifests
Import pre-built manifests from @mounaji_npm/saas-template/modules:
import {
// Individual manifests
HOME_MODULE,
DASHBOARD_MODULE,
CHAT_MODULE,
DISCOVER_MODULE,
ASSISTANTS_MODULE,
KNOWLEDGE_BASE_MODULE,
AGENT_WORKSPACE_MODULE,
INVENTORY_MODULE,
PLATFORMS_MODULE,
WORKFLOWS_MODULE,
TASKS_MODULE,
CONNECTIONS_MODULE,
EMAIL_MODULE,
CONTACTS_MODULE,
PRICING_MODULE,
API_KEYS_MODULE,
SETTINGS_MODULE,
HELP_MODULE,
// Bundle presets
ALL_MODULES, // all 16 platform modules
CORE_MODULES, // Home + Dashboard + Chat + Assistants + Knowledge Base + Settings
AI_BUNDLE, // Chat + Assistants + Knowledge Base + Agent Workspace
OPS_BUNDLE, // Workflows + Tasks + Connections + Platforms + Email
} from '@mounaji_npm/saas-template/modules';Manifest shape
{
id: string, // unique key, used as route key and React list key
label: string, // display name in sidebar
icon: string, // emoji or icon component
path: string, // route path — must match your router
section: string, // sidebar group label (e.g. 'Workspace', 'Tools')
order: number, // sort order within section (lower = higher up)
}Adding a Custom Page
Step 1 — Define a manifest:
// src/modules/analytics-module.js
export const ANALYTICS_MODULE = {
id: 'analytics',
label: 'Analytics',
icon: '📊',
path: '/analytics',
section: 'Workspace',
order: 15,
};Step 2 — Register it in AppShell:
import { ANALYTICS_MODULE } from './modules/analytics-module.js';
import { CORE_MODULES } from '@mounaji_npm/saas-template/modules';
<AppShell modules={[...CORE_MODULES, ANALYTICS_MODULE]}>
{children}
</AppShell>Step 3 — Create the page:
// app/analytics/page.js (Next.js) or src/pages/AnalyticsPage.jsx (Vite)
export default function AnalyticsPage() {
return <div>Analytics content</div>;
}Dynamic Module Registration (Plugin Pattern)
Register modules conditionally based on user role, plan, or feature flags. The module is added to the sidebar when the component mounts and removed when it unmounts.
import { useRegisterModule } from '@mounaji_npm/saas-template';
function AnalyticsPage() {
useRegisterModule({
id: 'analytics',
label: 'Analytics',
icon: '📊',
path: '/analytics',
section: 'Workspace',
order: 15,
});
return <div>Analytics content</div>;
}Useful for:
- Role-gated pages (only admins see "Admin Panel")
- Plan-gated features (only Pro users see "Advanced Analytics")
- Lazy-loaded feature modules
Module Registry Hooks
import {
useModuleRegistry,
useNavTree,
useRegisterModule,
ModuleRegistryProvider,
} from '@mounaji_npm/saas-template';
// Read all registered modules
function DebugNav() {
const { modules } = useModuleRegistry();
return <pre>{JSON.stringify(modules, null, 2)}</pre>;
}
// Get sorted sidebar tree (grouped by section, ordered by order)
function CustomSidebar() {
const navTree = useNavTree();
return navTree.map(section => (
<div key={section.label}>
<h3>{section.label}</h3>
{section.items.map(item => <a key={item.id} href={item.path}>{item.icon} {item.label}</a>)}
</div>
));
}Pre-built Pages
Import ready-made page components from @mounaji_npm/saas-template/modules:
import { DashboardPage, SettingsPage } from '@mounaji_npm/saas-template/modules';
// app/dashboard/page.js
export default DashboardPage;
// app/settings/page.js — includes live TokenEditor + export/import panels
export default SettingsPage;i18n Integration
Pass the i18n prop to AppShell to activate I18nProvider and inject LanguageSwitcher into the TopNav:
<AppShell
modules={CORE_MODULES}
activePath={pathname}
i18n={{
enabled: true,
defaultLocale: 'en',
locales: ['en', 'es', 'pt'],
show: true,
}}
>
{children}
</AppShell>| i18n field | Type | Default | Description |
|---|---|---|---|
| enabled | boolean | false | Activate i18n |
| defaultLocale | string | 'en' | Initial locale |
| locales | string[] | all | Available locales |
| show | boolean | true | Show LanguageSwitcher in TopNav |
| translations | object | — | Custom translation overrides |
Branding with mn-config.js
When scaffolded via @mounaji_npm/cli, a mn-config.js file is generated at the project root for branding and token overrides:
// mn-config.js
export default {
appName: 'My SaaS',
logo: null, // replace with <img src="..." /> or SVG
tokens: {
colorPrimary: '#7C3AED',
colorAccent: '#06B6D4',
fontFamily: '"Geist", system-ui, sans-serif',
radiusMd: '10px',
},
modules: [], // extra custom module manifests
};Import and use in your layout:
import config from '../mn-config.js';
import { CORE_MODULES } from '@mounaji_npm/saas-template/modules';
<AppShell
modules={[...CORE_MODULES, ...config.modules]}
tokens={config.tokens}
logo={config.logo}
>
{children}
</AppShell>