Skip to content

Commit 96638a6

Browse files
committed
fix: build issues
1 parent c93eee6 commit 96638a6

10 files changed

Lines changed: 366 additions & 33 deletions

File tree

src/app/(dashboard)/(modules)/chat/rooms/page.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getModules } from '@/lib/api/modules';
21
import { getRooms } from '@/lib/api/chat';
32
import Decimal from 'decimal.js';
43
import { ROOMS_LIMIT } from '@/components/chat/rooms/tables/rooms/utils';
@@ -16,11 +15,6 @@ type ChatRoomParams = {
1615

1716
export default async function ChatRooms(props: ChatRoomParams) {
1817
const searchParams = await props.searchParams;
19-
const modules = await getModules();
20-
const chatModuleAvailable = !!modules.find(
21-
m => m.moduleName === 'chat' && m.serving
22-
);
23-
if (!chatModuleAvailable) return <>Chat module is not serving.</>;
2418

2519
const skip = searchParams?.pageIndex
2620
? new Decimal(searchParams?.pageIndex).mul(ROOMS_LIMIT).toNumber()

src/app/(dashboard)/(modules)/chat/settings/page.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@ import { getModules } from '@/lib/api/modules';
55
export default async function ChatSettings() {
66
const { config: data } = await getChatSettings();
77
const modules = await getModules();
8-
const chatAvailable = !!modules.find(
9-
m => m.moduleName === 'chat' && m.serving
10-
);
118
const emailAvailable = !!modules.find(
129
m => m.moduleName === 'email' && m.serving
1310
);
1411
const pushNotificationsAvailable = !!modules.find(
1512
m => m.moduleName === 'pushNotifications' && m.serving
1613
);
17-
if (!chatAvailable) return <>Chat module is not serving.</>;
14+
1815
return (
1916
<Settings
2017
data={data}

src/app/(dashboard)/(modules)/email/templates/page.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getModules } from '@/lib/api/modules';
21
import { getExternalTemplates, getTemplates } from '@/lib/api/email';
32
import { TemplatesDashboard } from '@/components/email/templates/dashboard';
43
import { EmailEditorWrapper } from '@/components/email/email-editor';
@@ -18,12 +17,7 @@ export default async function EmailTemplates(
1817
props: Readonly<EmailTemplatesParams>
1918
) {
2019
const searchParams = await props.searchParams;
21-
const modules = await getModules();
22-
const emailModuleAvailable = !!modules.find(
23-
m => m.moduleName === 'email' && m.serving
24-
);
2520
const openEditor = searchParams['editor-open'] === 'true';
26-
if (!emailModuleAvailable) return <>Email module is not serving.</>;
2721

2822
const templates = await getTemplates({
2923
skip: searchParams.pageIndex ? searchParams.pageIndex * 10 : 0,

src/app/(dashboard)/(modules)/storage/browse/layout.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { FileSystemActionsProvider } from '@/components/storage/FileSystemActionsProvider';
2-
import { getModules } from '@/lib/api/modules';
32

43
type LayoutProps = {
54
children: React.ReactNode;
@@ -12,11 +11,6 @@ export default async function Layout({
1211
files,
1312
folders,
1413
}: LayoutProps) {
15-
const modules = await getModules();
16-
const storageModuleAvailable = !!modules.find(
17-
m => m.moduleName === 'storage' && m.serving
18-
);
19-
if (!storageModuleAvailable) return <>Storage module is not serving.</>;
2014
return (
2115
<FileSystemActionsProvider>
2216
<div className="space-y-5">

src/app/(dashboard)/layout.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import { SidebarProvider } from '@/components/ui/sidebar';
22
import { AppSidebar } from '@/components/navigation/appSidebar';
3+
import { ModuleAvailabilityProvider } from '@/contexts/ModuleAvailabilityContext';
4+
import { ModuleGuard } from '@/components/module-guard/ModuleGuard';
35

46
export default function Layout({
57
children,
68
}: Readonly<{ children: React.ReactNode }>) {
79
return (
8-
<SidebarProvider>
9-
<AppSidebar />
10-
<main
11-
className="relative flex flex-col flex-1 min-h-svh bg-background',
12-
'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))]"
13-
>
14-
{children}
15-
</main>
16-
</SidebarProvider>
10+
<ModuleAvailabilityProvider>
11+
<SidebarProvider>
12+
<AppSidebar />
13+
<main
14+
className="relative flex flex-col flex-1 min-h-svh bg-background',
15+
'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))]"
16+
>
17+
<ModuleGuard>{children}</ModuleGuard>
18+
</main>
19+
</SidebarProvider>
20+
</ModuleAvailabilityProvider>
1721
);
1822
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use client';
2+
3+
import { usePathname } from 'next/navigation';
4+
import { useModuleAvailability } from '@/contexts/ModuleAvailabilityContext';
5+
import {
6+
getModuleNameFromPath,
7+
getModuleDisplayName,
8+
} from '@/lib/utils/module-utils';
9+
import { ModuleNotFound } from '@/components/module-not-found/ModuleNotFound';
10+
import { Skeleton } from '@/components/ui/skeleton';
11+
12+
interface ModuleGuardProps {
13+
children: React.ReactNode;
14+
}
15+
16+
export function ModuleGuard({ children }: ModuleGuardProps) {
17+
const pathname = usePathname();
18+
const { modules, isLoading, isModuleAvailable, isModuleServing } =
19+
useModuleAvailability();
20+
21+
// Get module name from current path
22+
const moduleName = getModuleNameFromPath(pathname);
23+
24+
// If not a module path, loading, or settings route, render children
25+
if (!moduleName || isLoading || pathname.startsWith('/settings')) {
26+
if (isLoading) {
27+
return (
28+
<div className="flex items-center justify-center min-h-[60vh] p-4">
29+
<div className="space-y-4 w-full max-w-md">
30+
<Skeleton className="h-8 w-full" />
31+
<Skeleton className="h-4 w-3/4" />
32+
<Skeleton className="h-10 w-full" />
33+
</div>
34+
</div>
35+
);
36+
}
37+
return <>{children}</>;
38+
}
39+
40+
// Check if module is available and serving
41+
const available = isModuleAvailable(moduleName);
42+
const serving = isModuleServing(moduleName);
43+
44+
// If module is not available or not serving, show module not found
45+
if (!available || !serving) {
46+
// Map the API module name to the URL path for display name lookup
47+
const urlPath = pathname.split('/')[1];
48+
const displayName = getModuleDisplayName(urlPath);
49+
return (
50+
<ModuleNotFound
51+
moduleName={displayName}
52+
isAvailable={available}
53+
isServing={serving}
54+
/>
55+
);
56+
}
57+
58+
// Module is available and serving, render children
59+
return <>{children}</>;
60+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
'use client';
2+
3+
import { AlertTriangle, Home, Settings } from 'lucide-react';
4+
import Link from 'next/link';
5+
import { Button } from '@/components/ui/button';
6+
import {
7+
Card,
8+
CardContent,
9+
CardDescription,
10+
CardHeader,
11+
CardTitle,
12+
} from '@/components/ui/card';
13+
14+
interface ModuleNotFoundProps {
15+
moduleName: string;
16+
isAvailable: boolean;
17+
isServing: boolean;
18+
}
19+
20+
export function ModuleNotFound({
21+
moduleName,
22+
isAvailable,
23+
isServing,
24+
}: ModuleNotFoundProps) {
25+
const getTitle = () => {
26+
if (!isAvailable) {
27+
return `${moduleName} Module Not Deployed`;
28+
}
29+
if (!isServing) {
30+
return `${moduleName} Module Not Available`;
31+
}
32+
return `${moduleName} Module Not Found`;
33+
};
34+
35+
const getDescription = () => {
36+
if (!isAvailable) {
37+
return `The ${moduleName} module is not currently deployed in your system. This module needs to be installed and configured on the backend before it can be accessed through the UI.`;
38+
}
39+
if (!isServing) {
40+
return `The ${moduleName} module is deployed but not currently serving. Please check the module configuration and ensure it's properly set up.`;
41+
}
42+
return `The ${moduleName} module could not be found. Please check your configuration.`;
43+
};
44+
45+
const getActionText = () => {
46+
if (!isAvailable) {
47+
return 'Return to Home';
48+
}
49+
if (!isServing) {
50+
return 'Go to Settings';
51+
}
52+
return 'Return to Home';
53+
};
54+
55+
const getActionLink = () => {
56+
if (!isAvailable) {
57+
return '/';
58+
}
59+
if (!isServing) {
60+
return '/settings/general';
61+
}
62+
return '/';
63+
};
64+
65+
const getActionIcon = () => {
66+
if (!isAvailable) {
67+
return Home;
68+
}
69+
if (!isServing) {
70+
return Settings;
71+
}
72+
return Home;
73+
};
74+
75+
const ActionIcon = getActionIcon();
76+
77+
return (
78+
<div className="flex items-center justify-center min-h-[60vh] p-4">
79+
<Card className="w-full max-w-md">
80+
<CardHeader className="text-center">
81+
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-yellow-100">
82+
<AlertTriangle className="h-6 w-6 text-yellow-600" />
83+
</div>
84+
<CardTitle className="text-xl">{getTitle()}</CardTitle>
85+
<CardDescription className="text-sm text-muted-foreground">
86+
{getDescription()}
87+
</CardDescription>
88+
</CardHeader>
89+
<CardContent className="text-center">
90+
<Button asChild className="w-full">
91+
<Link href={getActionLink()}>
92+
<ActionIcon className="mr-2 h-4 w-4" />
93+
{getActionText()}
94+
</Link>
95+
</Button>
96+
</CardContent>
97+
</Card>
98+
</div>
99+
);
100+
}

src/components/navigation/appSidebar.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,24 @@ import Image from 'next/image';
1616
import { NavUser } from './navUser';
1717
import { navList } from './navList.config';
1818
import { NavEnv } from '@/components/navigation/navEnv';
19+
import { useModuleAvailability } from '@/contexts/ModuleAvailabilityContext';
20+
import { filterNavigationByModules } from '@/lib/utils/module-utils';
21+
import { Skeleton } from '@/components/ui/skeleton';
1922

2023
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
24+
const { modules, isLoading } = useModuleAvailability();
25+
26+
// Filter navigation items based on available modules
27+
const filteredNavMain = React.useMemo(() => {
28+
if (isLoading) return navList.navMain;
29+
return filterNavigationByModules(navList.navMain, modules);
30+
}, [modules, isLoading]);
31+
32+
const filteredNavSecondary = React.useMemo(() => {
33+
if (isLoading) return navList.navSecondary;
34+
return filterNavigationByModules(navList.navSecondary, modules);
35+
}, [modules, isLoading]);
36+
2137
return (
2238
<Sidebar variant="inset" {...props} className="border-r border-r-border">
2339
<SidebarHeader>
@@ -36,8 +52,25 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
3652
</SidebarMenu>
3753
</SidebarHeader>
3854
<SidebarContent className="main-scrollbar ">
39-
<NavGroup items={navList.navMain} label="Modules" />
40-
<NavGroup items={navList.navSecondary} className="mt-3" label="Tools" />
55+
{isLoading ? (
56+
<div className="space-y-4 p-4">
57+
<Skeleton className="h-4 w-24" />
58+
<div className="space-y-2">
59+
<Skeleton className="h-10 w-full" />
60+
<Skeleton className="h-10 w-full" />
61+
<Skeleton className="h-10 w-full" />
62+
</div>
63+
</div>
64+
) : (
65+
<>
66+
<NavGroup items={filteredNavMain} label="Modules" />
67+
<NavGroup
68+
items={filteredNavSecondary}
69+
className="mt-3"
70+
label="Tools"
71+
/>
72+
</>
73+
)}
4174
</SidebarContent>
4275
<SidebarFooter>
4376
<NavEnv />
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use client';
2+
3+
import React, { createContext, useContext, useEffect, useState } from 'react';
4+
import { Module } from '@/lib/models/Module';
5+
import { getModules } from '@/lib/api/modules';
6+
7+
interface ModuleAvailabilityContextType {
8+
modules: Module[];
9+
isLoading: boolean;
10+
isModuleAvailable: (moduleName: string) => boolean;
11+
isModuleServing: (moduleName: string) => boolean;
12+
}
13+
14+
const ModuleAvailabilityContext = createContext<
15+
ModuleAvailabilityContextType | undefined
16+
>(undefined);
17+
18+
export function ModuleAvailabilityProvider({
19+
children,
20+
}: {
21+
children: React.ReactNode;
22+
}) {
23+
const [modules, setModules] = useState<Module[]>([]);
24+
const [isLoading, setIsLoading] = useState(true);
25+
26+
useEffect(() => {
27+
const fetchModules = async () => {
28+
try {
29+
const availableModules = await getModules();
30+
setModules(availableModules);
31+
} catch (error) {
32+
console.error('Failed to fetch modules:', error);
33+
setModules([]);
34+
} finally {
35+
setIsLoading(false);
36+
}
37+
};
38+
39+
fetchModules();
40+
}, []);
41+
42+
const isModuleAvailable = (moduleName: string): boolean => {
43+
return modules.some(module => module.moduleName === moduleName);
44+
};
45+
46+
const isModuleServing = (moduleName: string): boolean => {
47+
return modules.some(
48+
module => module.moduleName === moduleName && module.serving
49+
);
50+
};
51+
52+
const value: ModuleAvailabilityContextType = {
53+
modules,
54+
isLoading,
55+
isModuleAvailable,
56+
isModuleServing,
57+
};
58+
59+
return (
60+
<ModuleAvailabilityContext.Provider value={value}>
61+
{children}
62+
</ModuleAvailabilityContext.Provider>
63+
);
64+
}
65+
66+
export function useModuleAvailability() {
67+
const context = useContext(ModuleAvailabilityContext);
68+
if (context === undefined) {
69+
throw new Error(
70+
'useModuleAvailability must be used within a ModuleAvailabilityProvider'
71+
);
72+
}
73+
return context;
74+
}

0 commit comments

Comments
 (0)