feat(03-03): module loader utility with category page and lazy cards

- Create module-loader.ts with MODULE_REGISTRY mapping slugs to dynamic imports (ssr:false)
- Create [category] page fetching active modules from API, filtering by category
- Create ModuleCard component with icon, name, description, and link to expanded view
- Add i18n keys for modules namespace (de + en)
This commit is contained in:
2026-06-19 13:36:09 +02:00
parent 629ef81ea0
commit a191628fa5
5 changed files with 334 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
/**
* Module Loader — Lazy-loading registry for Tessera modules.
*
* Maps module slugs to dynamically imported components.
* Only modules listed in MODULE_REGISTRY will have their code loaded;
* inactive/unregistered modules never trigger an import (MOD-04).
*
* Security: Only whitelisted slugs are resolved — arbitrary slugs
* from URL params return null (T-03-09).
*/
import dynamic from 'next/dynamic';
import type { ComponentType } from 'react';
/**
* Registry entry for a module's lazy-loaded components.
*/
interface ModuleRegistryEntry {
/** Main module page component (loaded on demand) */
component: ComponentType;
/** Optional card component for category grid view */
cardComponent?: ComponentType;
}
/**
* Module registry mapping slugs to lazy-loaded components.
*
* To register a new module, add its slug and dynamic imports here.
* Components are only fetched when React renders them (code-splitting).
*/
export const MODULE_REGISTRY: Record<string, ModuleRegistryEntry> = {
domaincheck: {
component: dynamic(
() => import('@/app/(portal)/modules/domaincheck/page'),
{ ssr: false },
),
},
};
/**
* Load the main component for a module by slug.
*
* @param slug - Module slug (e.g., 'domaincheck')
* @returns The dynamic component, or null if the slug is not registered
*/
export function loadModuleComponent(
slug: string,
): ComponentType | null {
const entry = MODULE_REGISTRY[slug];
return entry?.component ?? null;
}
/**
* Load the card component for a module by slug.
*
* Falls back to null if no card component is registered.
*
* @param slug - Module slug (e.g., 'domaincheck')
* @returns The card component, or null if not available
*/
export function loadModuleCard(
slug: string,
): ComponentType | null {
const entry = MODULE_REGISTRY[slug];
return entry?.cardComponent ?? null;
}