2026-03-09 11:18:22 +01:00
|
|
|
import {
|
|
|
|
|
AttachmentIcon,
|
|
|
|
|
CommunityIcon,
|
|
|
|
|
CourseIcon,
|
|
|
|
|
DashboardIcon,
|
|
|
|
|
PeopleIcon,
|
|
|
|
|
SettingsIcon,
|
|
|
|
|
SetupIcon
|
|
|
|
|
} from '@cio/ui/custom/moving-icons';
|
|
|
|
|
|
2025-12-22 02:36:48 +01:00
|
|
|
import type { AccountOrg } from '$features/app/types';
|
2025-11-24 01:00:50 +01:00
|
|
|
import type { Component } from 'svelte';
|
2026-03-13 18:40:07 +01:00
|
|
|
import BracesIcon from '@lucide/svelte/icons/braces';
|
2026-03-09 11:18:22 +01:00
|
|
|
import TagIcon from '@lucide/svelte/icons/tag';
|
2025-11-24 01:00:50 +01:00
|
|
|
import { isActive } from '$lib/utils/functions/app';
|
|
|
|
|
|
|
|
|
|
export interface NavItem {
|
|
|
|
|
title: string; // translation key
|
|
|
|
|
url: string;
|
|
|
|
|
path: string; // Actual path (e.g., '/settings') for breadcrumb generation
|
|
|
|
|
icon?: Component;
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
isExpanded?: boolean;
|
|
|
|
|
items?: NavItem[]; // for nested items like settings
|
2026-03-09 11:18:22 +01:00
|
|
|
isPaid?: boolean; // Show upgrade indicator for free plan users
|
2026-03-13 18:40:07 +01:00
|
|
|
disabled?: boolean;
|
2025-11-24 01:00:50 +01:00
|
|
|
// Metadata for breadcrumb generation
|
|
|
|
|
useHashUrl?: boolean; // Use '#' as URL (for collapsible items like settings)
|
|
|
|
|
nestedRoutes?: NestedRouteConfig[]; // Static nested routes (like community/ask, settings/customize-lms)
|
|
|
|
|
supportsDynamicSegment?: boolean; // Supports dynamic segments (like [slug])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface NavItemConfig {
|
|
|
|
|
titleKey: string;
|
|
|
|
|
path: string;
|
|
|
|
|
icon?: Component;
|
|
|
|
|
requiresAdmin?: boolean;
|
2026-03-13 18:40:07 +01:00
|
|
|
disableWhenNotAdmin?: boolean;
|
2025-11-24 01:00:50 +01:00
|
|
|
items?: NavItemConfig[];
|
|
|
|
|
useHashUrl?: boolean; // Use '#' as URL (for collapsible items like settings)
|
|
|
|
|
nestedRoutes?: NestedRouteConfig[]; // Static nested routes
|
|
|
|
|
supportsDynamicSegment?: boolean; // Supports dynamic segments (like [slug])
|
2025-12-02 09:54:38 +01:00
|
|
|
matchPattern?: string | ((orgSlug: string) => string); // Regex pattern for route matching
|
2026-03-09 11:18:22 +01:00
|
|
|
isPaid?: boolean; // Show upgrade indicator for free plan users
|
2025-11-24 01:00:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface NestedRouteConfig {
|
|
|
|
|
path: string; // Relative to parent (e.g., 'ask', 'customize-lms')
|
|
|
|
|
titleKey: string; // Translation key or plain text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Base navigation configuration structure
|
|
|
|
|
const baseNavConfig: NavItemConfig[] = [
|
|
|
|
|
{
|
|
|
|
|
titleKey: 'org_navigation.dashboard',
|
|
|
|
|
path: '',
|
2026-03-09 11:18:22 +01:00
|
|
|
icon: DashboardIcon,
|
2025-12-02 09:54:38 +01:00
|
|
|
matchPattern: '^/org/[^/]+/?$' // Exact match only
|
2025-11-24 01:00:50 +01:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
titleKey: 'org_navigation.courses',
|
|
|
|
|
path: '/courses',
|
2026-03-09 11:18:22 +01:00
|
|
|
icon: CourseIcon,
|
2025-12-02 09:54:38 +01:00
|
|
|
matchPattern: '^/org/[^/]+/courses(/.*)?$' // Matches nested routes
|
2025-11-24 01:00:50 +01:00
|
|
|
},
|
2026-03-09 11:18:22 +01:00
|
|
|
{
|
|
|
|
|
titleKey: 'org_navigation.tags',
|
|
|
|
|
path: '/tags',
|
|
|
|
|
icon: TagIcon,
|
|
|
|
|
requiresAdmin: true,
|
|
|
|
|
matchPattern: '^/org/[^/]+/tags(/.*)?$'
|
|
|
|
|
},
|
2025-11-24 01:00:50 +01:00
|
|
|
{
|
|
|
|
|
titleKey: 'org_navigation.community',
|
|
|
|
|
path: '/community',
|
2026-03-09 11:18:22 +01:00
|
|
|
icon: CommunityIcon,
|
2025-11-24 01:00:50 +01:00
|
|
|
supportsDynamicSegment: true, // Supports /community/[slug]
|
2025-12-02 09:54:38 +01:00
|
|
|
matchPattern: '^/org/[^/]+/community(/.*)?$', // Matches nested routes
|
2025-11-24 01:00:50 +01:00
|
|
|
nestedRoutes: [
|
|
|
|
|
{
|
|
|
|
|
path: 'ask',
|
|
|
|
|
titleKey: 'Ask Question' // Could be translated
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
titleKey: 'org_navigation.audience',
|
|
|
|
|
path: '/audience',
|
2026-03-09 11:18:22 +01:00
|
|
|
icon: PeopleIcon,
|
2025-12-02 09:54:38 +01:00
|
|
|
matchPattern: '^/org/[^/]+/audience(/.*)?$' // Matches nested routes
|
2025-11-24 01:00:50 +01:00
|
|
|
},
|
2026-03-09 11:18:22 +01:00
|
|
|
{
|
|
|
|
|
titleKey: 'org_navigation.media',
|
|
|
|
|
path: '/media',
|
|
|
|
|
icon: AttachmentIcon,
|
|
|
|
|
matchPattern: '^/org/[^/]+/media(/.*)?$'
|
|
|
|
|
},
|
2025-11-24 01:00:50 +01:00
|
|
|
{
|
|
|
|
|
titleKey: 'org_navigation.setup',
|
|
|
|
|
path: '/setup',
|
2026-03-09 11:18:22 +01:00
|
|
|
icon: SetupIcon,
|
2025-12-02 09:54:38 +01:00
|
|
|
requiresAdmin: true,
|
|
|
|
|
matchPattern: '^/org/[^/]+/setup(/.*)?$' // Matches nested routes
|
2025-11-24 01:00:50 +01:00
|
|
|
},
|
2026-03-13 18:40:07 +01:00
|
|
|
{
|
|
|
|
|
titleKey: 'org_navigation.automation',
|
|
|
|
|
path: '/automation',
|
|
|
|
|
icon: BracesIcon,
|
|
|
|
|
requiresAdmin: true,
|
|
|
|
|
disableWhenNotAdmin: true,
|
2026-03-18 23:39:12 +01:00
|
|
|
useHashUrl: true,
|
|
|
|
|
matchPattern: '^/org/[^/]+/automation(/.*)?$',
|
|
|
|
|
items: [
|
|
|
|
|
{
|
|
|
|
|
titleKey: 'automation.tabs.mcp',
|
|
|
|
|
path: '/automation/mcp'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
titleKey: 'automation.tabs.zapier',
|
|
|
|
|
path: '/automation/zapier'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
titleKey: 'automation.tabs.api',
|
|
|
|
|
path: '/automation/api'
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
nestedRoutes: [
|
|
|
|
|
{
|
|
|
|
|
path: 'mcp',
|
|
|
|
|
titleKey: 'automation.tabs.mcp'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'zapier',
|
|
|
|
|
titleKey: 'automation.tabs.zapier'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'api',
|
|
|
|
|
titleKey: 'automation.tabs.api'
|
|
|
|
|
}
|
|
|
|
|
]
|
2026-03-13 18:40:07 +01:00
|
|
|
},
|
2025-11-24 01:00:50 +01:00
|
|
|
{
|
|
|
|
|
titleKey: 'org_navigation.settings',
|
|
|
|
|
path: '/settings',
|
2026-03-09 11:18:22 +01:00
|
|
|
icon: SettingsIcon,
|
2025-11-24 01:00:50 +01:00
|
|
|
useHashUrl: true, // Use '#' for collapsible parent
|
2025-12-02 09:54:38 +01:00
|
|
|
matchPattern: '^/org/[^/]+/settings(/.*)?$', // Matches nested routes
|
2025-11-24 01:00:50 +01:00
|
|
|
items: [
|
|
|
|
|
{
|
2026-03-09 11:18:22 +01:00
|
|
|
titleKey: 'settings.tabs.profile_tab',
|
2025-11-24 01:00:50 +01:00
|
|
|
path: '/settings'
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-03-09 11:18:22 +01:00
|
|
|
titleKey: 'settings.tabs.organization_tab',
|
2025-12-02 09:54:38 +01:00
|
|
|
path: '/settings/org'
|
2025-11-24 01:00:50 +01:00
|
|
|
},
|
|
|
|
|
{
|
2026-03-09 11:18:22 +01:00
|
|
|
titleKey: 'settings.tabs.landing_page_tab',
|
2025-12-02 09:54:38 +01:00
|
|
|
path: '/settings/landingpage'
|
2025-11-24 01:00:50 +01:00
|
|
|
},
|
|
|
|
|
{
|
2026-03-09 11:18:22 +01:00
|
|
|
titleKey: 'settings.tabs.billing_tab',
|
2025-12-02 09:54:38 +01:00
|
|
|
path: '/settings/billing'
|
2026-03-09 11:18:22 +01:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
titleKey: 'settings.tabs.auth_tab',
|
|
|
|
|
matchPattern: '^/org/[^/]+/settings/auth(/.*)?$',
|
|
|
|
|
path: '/settings/auth',
|
|
|
|
|
isPaid: true
|
2025-11-24 01:00:50 +01:00
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
nestedRoutes: [
|
|
|
|
|
{
|
|
|
|
|
path: 'customize-lms',
|
2026-03-09 11:18:22 +01:00
|
|
|
titleKey: 'settings.tabs.customize_lms_tab'
|
2025-11-24 01:00:50 +01:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'domains',
|
2026-03-09 11:18:22 +01:00
|
|
|
titleKey: 'settings.tabs.domains_tab'
|
2025-11-24 01:00:50 +01:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'teams',
|
2026-03-09 11:18:22 +01:00
|
|
|
titleKey: 'settings.tabs.teams_tab'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'auth',
|
|
|
|
|
titleKey: 'settings.tabs.auth_tab'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'auth/sso',
|
|
|
|
|
titleKey: 'settings.tabs.sso_tab'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'auth/token-auth',
|
|
|
|
|
titleKey: 'settings.tabs.token_auth_tab'
|
2025-11-24 01:00:50 +01:00
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get navigation items based on organization context and permissions
|
|
|
|
|
*/
|
|
|
|
|
export function getOrgNavigationItems(
|
|
|
|
|
currentOrgPath: string,
|
|
|
|
|
currentOrg: AccountOrg,
|
|
|
|
|
isOrgAdmin: boolean | null,
|
|
|
|
|
t: (key: string) => string,
|
|
|
|
|
pagePathname: string
|
|
|
|
|
): NavItem[] {
|
|
|
|
|
const items: NavItem[] = [];
|
|
|
|
|
|
|
|
|
|
for (const config of baseNavConfig) {
|
|
|
|
|
// Skip admin-only items if user is not admin
|
2026-03-13 18:40:07 +01:00
|
|
|
if (config.requiresAdmin && !isOrgAdmin && !config.disableWhenNotAdmin) {
|
2025-11-24 01:00:50 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const url = config.path === '' ? currentOrgPath : `${currentOrgPath}${config.path}`;
|
|
|
|
|
const fullPath = config.path === '' ? `/org/${currentOrg.siteName}` : `/org/${currentOrg.siteName}${config.path}`;
|
|
|
|
|
|
2025-12-02 09:54:38 +01:00
|
|
|
// Extract match pattern (handle function case)
|
|
|
|
|
const matchPattern =
|
2026-03-09 11:18:22 +01:00
|
|
|
typeof config.matchPattern === 'function' ? config.matchPattern(currentOrg.siteName!) : config.matchPattern;
|
2025-12-02 09:54:38 +01:00
|
|
|
|
2025-11-24 01:00:50 +01:00
|
|
|
const item: NavItem = {
|
|
|
|
|
title: t(config.titleKey),
|
|
|
|
|
url: config.useHashUrl ? '#' : url,
|
|
|
|
|
path: config.path, // Store actual path for breadcrumb generation
|
|
|
|
|
icon: config.icon,
|
2025-12-02 09:54:38 +01:00
|
|
|
isActive: isActive(pagePathname, fullPath, matchPattern),
|
|
|
|
|
isExpanded: config.items ? isActive(pagePathname, fullPath, matchPattern) : undefined,
|
2026-03-13 18:40:07 +01:00
|
|
|
disabled: Boolean(config.disableWhenNotAdmin && !isOrgAdmin),
|
2025-11-24 01:00:50 +01:00
|
|
|
useHashUrl: config.useHashUrl,
|
|
|
|
|
nestedRoutes: config.nestedRoutes,
|
2026-03-09 11:18:22 +01:00
|
|
|
supportsDynamicSegment: config.supportsDynamicSegment,
|
|
|
|
|
isPaid: config.isPaid
|
2025-11-24 01:00:50 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Handle nested items (like settings sub-items)
|
|
|
|
|
if (config.items) {
|
2026-03-09 11:18:22 +01:00
|
|
|
item.items = config.items.map((subConfig) => {
|
|
|
|
|
const matchPattern =
|
|
|
|
|
typeof subConfig.matchPattern === 'function'
|
|
|
|
|
? subConfig.matchPattern(currentOrg.siteName!)
|
|
|
|
|
: subConfig.matchPattern;
|
|
|
|
|
const url = `${currentOrgPath}${subConfig.path}`;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
title: t(subConfig.titleKey),
|
|
|
|
|
isActive: isActive(pagePathname, url, matchPattern, true),
|
|
|
|
|
url: url,
|
|
|
|
|
path: subConfig.path,
|
|
|
|
|
isPaid: subConfig.isPaid
|
|
|
|
|
};
|
|
|
|
|
});
|
2025-11-24 01:00:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
items.push(item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return items;
|
|
|
|
|
}
|