# Privilege and Language Flow - Complete Trace (dateien.ts Example) ## πŸ“‹ Overview This document traces the **complete flow** of privilege checking and language resolution from PageManager through to rendered content, using `dateien.ts` as a concrete example. --- ## πŸ”„ Complete Flow Diagram ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 1. USER NAVIGATES TO /verwaltung/dateien β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 2. PageManager.tsx - useEffect triggered β”‚ β”‚ Line 67: const pageData = getPageDataByPath(currentPath)β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 3. data/pages/index.ts - getPageDataByPath() β”‚ β”‚ Line 27-29: Find page by path β”‚ β”‚ Returns: dateienPageData object β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 4. PageManager.tsx - Check if module enabled β”‚ β”‚ Line 70: if (!pageData.moduleEnabled) return β”‚ β”‚ dateien.ts Line 248: moduleEnabled: true βœ… β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 5. PageManager.tsx - Check Page Privilege β”‚ β”‚ Line 75: checkPageAccess(pageData) β”‚ β”‚ ↓ β”‚ β”‚ Line 29-40: async checkPageAccess() β”‚ β”‚ if (!pageData.privilegeChecker) return true β”‚ β”‚ else return await pageData.privilegeChecker() β”‚ β”‚ ↓ β”‚ β”‚ dateien.ts Line 243: privilegeChecker: privilegeCheckers.viewerRole β”‚ β”‚ ↓ β”‚ β”‚ privilegeCheckers.ts Line 199-208: β”‚ β”‚ createRolePrivilegeChecker(['viewer', 'user', 'admin', 'sysadmin']) β”‚ β”‚ Reads from localStorage('currentUser').privilege β”‚ β”‚ Returns true if user privilege matches βœ… β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 6. PageManager.tsx - Get Current Language β”‚ β”‚ Line 20: const { currentLanguage } = useLanguage() β”‚ β”‚ ↓ β”‚ β”‚ LanguageContext reads from: β”‚ β”‚ localStorage('currentUser').language β”‚ β”‚ Current language: 'de' | 'en' | 'fr' β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 7. PageManager.tsx - Create Page Instance β”‚ β”‚ Line 93-116: Create PageInstance β”‚ β”‚ Line 101-108: Render PageRenderer with: β”‚ β”‚ - pageData (full dateienPageData object) β”‚ β”‚ - language={currentLanguage} βœ… β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 8. PageRenderer.tsx - Receive Props β”‚ β”‚ Line 13-17: PageRendererProps β”‚ β”‚ - pageData: GenericPageData β”‚ β”‚ - language: 'de' | 'en' | 'fr' βœ… β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 9. PageRenderer.tsx - Initialize Hook Factory β”‚ β”‚ Line 20-34: Execute hook factory β”‚ β”‚ ↓ β”‚ β”‚ dateien.ts Line 8-62: createFilesHook() β”‚ β”‚ Returns hook function that calls: β”‚ β”‚ - useUserFiles() β†’ fetches files data β”‚ β”‚ - useFileOperations() β†’ handles file operations β”‚ β”‚ Returns: hookData with data, operations, states β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 10. PageRenderer.tsx - Render Page Header β”‚ β”‚ Line 190-191: Render title β”‚ β”‚ resolveLanguageText(pageData.title, language) β”‚ β”‚ ↓ β”‚ β”‚ dateien.ts Line 141-145: title object β”‚ β”‚ { de: 'Dateien', en: 'Files', fr: 'Fichiers' } β”‚ β”‚ ↓ β”‚ β”‚ pageInterface.ts Line 87-91: resolveLanguageText() β”‚ β”‚ Returns: text[language] β†’ 'Dateien' βœ… β”‚ β”‚ ↓ β”‚ β”‚ Line 192-193: Render subtitle (same process) β”‚ β”‚ Result: 'Verwalten Sie Ihre Dateien...' βœ… β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 11. PageRenderer.tsx - Render Header Buttons β”‚ β”‚ Line 198-234: Loop through headerButtons β”‚ β”‚ ↓ β”‚ β”‚ dateien.ts Line 153-165: Upload button config β”‚ β”‚ label: { de: 'Datei hochladen', ... } β”‚ β”‚ ↓ β”‚ β”‚ Line 230: resolveLanguageText(button.label, language) β”‚ β”‚ Result: 'Datei hochladen' βœ… β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 12. PageRenderer.tsx - Render Table Content β”‚ β”‚ Line 115-177: Render table type content β”‚ β”‚ ↓ β”‚ β”‚ dateien.ts Line 169-239: Table configuration β”‚ β”‚ - hookFactory: createFilesHook β”‚ β”‚ - columns: filesColumns (Line 65-124) β”‚ β”‚ Each column has: β”‚ β”‚ label: { de: '...', en: '...', fr: '...' } β”‚ β”‚ - actionButtons: [view, edit, download, delete] β”‚ β”‚ Each button has: β”‚ β”‚ title: { de: '...', en: '...', fr: '...' } β”‚ β”‚ ↓ β”‚ β”‚ Line 140: const columns = hookData.columns || configColumns β”‚ β”‚ columns = filesColumns (LanguageText objects!) β”‚ β”‚ ↓ β”‚ β”‚ Line 142-146: Resolve column labels βœ… β”‚ β”‚ resolvedColumns with label: string β”‚ β”‚ ↓ β”‚ β”‚ Line 150-165: Map action buttons β”‚ β”‚ title: resolveLanguageText(action.title, language) βœ…β”‚ β”‚ ↓ β”‚ β”‚ Line 174-181: Pass to FormGenerator β”‚ β”‚ columns={resolvedColumns} ← RESOLVED strings! βœ… β”‚ β”‚ actionButtons={formGeneratorActions} ← RESOLVED! βœ… β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 13. FormGenerator.tsx - Receive Props β”‚ β”‚ Line 81-104: FormGeneratorProps β”‚ β”‚ columns: ColumnConfig[] with label: string β”‚ β”‚ NOW receiving: label: string (resolved!) βœ… β”‚ β”‚ ↓ β”‚ β”‚ Line 105: const { t } = useLanguage() β”‚ β”‚ Has access to t() and currentLanguage βœ… β”‚ β”‚ ↓ β”‚ β”‚ Line 627, 642: Uses column.label directly β”‚ β”‚ Displays: 'Dateiname' (correct text!) βœ… β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 14. Action Buttons Rendering β”‚ β”‚ Line 766-785: Map through actionButtons β”‚ β”‚ ↓ β”‚ β”‚ Line 767-769: Get title β”‚ β”‚ actionTitle = actionButton.title (string!) βœ… β”‚ β”‚ ↓ β”‚ β”‚ Passed to EditActionButton, DeleteActionButton, etc. β”‚ β”‚ ↓ β”‚ β”‚ EditActionButton.tsx Line 39: title prop (string) β”‚ β”‚ Receives correct string! βœ… β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## 🎯 Privilege Checking - Detailed ### βœ… Where Privilege Checks Happen #### 1. **Page Level Check** (`PageManager.tsx` Line 75) ```typescript // PageManager.tsx const checkPageAccess = async (pageData: GenericPageData): Promise => { if (!pageData.privilegeChecker) { return true; // No checker = accessible to all } try { return await pageData.privilegeChecker(); } catch (error) { console.error(`Error checking page access for ${pageData.path}:`, error); return false; } }; ``` **For dateien.ts:** ```typescript // Line 243 privilegeChecker: privilegeCheckers.viewerRole ``` **Privilege Checker Implementation:** ```typescript // privilegeCheckers.ts Lines 199-208 viewerRole: createRolePrivilegeChecker( ['viewer', 'user', 'admin', 'sysadmin'], () => { const userPrivilege = getCurrentUserPrivilege(); // Reads from localStorage return Promise.resolve(userPrivilege ? [userPrivilege] : []); } ) ``` **Process:** 1. Read `localStorage.getItem('currentUser')` 2. Parse JSON and extract `user.privilege` 3. Check if privilege is in allowed list: `['viewer', 'user', 'admin', 'sysadmin']` 4. Return `true` if match, `false` otherwise #### 2. **Button Level Check** (`PageRenderer.tsx` Line 40) ```typescript const handleButtonClick = async (button: PageButton) => { try { // Check privilege if required if (button.privilegeChecker) { const hasPrivilege = await button.privilegeChecker(); if (!hasPrivilege) { console.warn(`Access denied for button: ${button.id}`); return; } } // Execute onClick... } }; ``` **Example from example-page.ts:** ```typescript { id: 'delete-all', label: 'Delete All', onClick: () => { /* ... */ }, privilegeChecker: privilegeCheckers.adminRole // Only admins } ``` #### 3. **Content Level Check** (`PageRenderer.tsx` Line 245) ```typescript {pageData.content?.map((content) => { // Check privilege for content if (content.privilegeChecker) { // Content is rendered only if privilege check passes return renderContent(content); } return renderContent(content); })} ``` ### βœ… Timing of Privilege Checks ``` User navigates β†’ PageManager useEffect triggers ↓ getPageDataByPath(currentPath) - fetches page config ↓ checkPageAccess(pageData) - ASYNC check ↓ If hasAccess = false β†’ Return early (no render) If hasAccess = true β†’ Create PageInstance β†’ Render PageRenderer ↓ Button clicks β†’ Check button.privilegeChecker before executing ``` **Key Point:** Privilege checks are **asynchronous** and happen **before** page rendering. --- ## 🌍 Language Resolution - Detailed ### βœ… Where Language IS Resolved Correctly #### 1. **Page Title and Subtitle** (`PageRenderer.tsx` Lines 191-193) ```typescript // PageRenderer receives: language = 'de' (from LanguageContext)

{resolveLanguageText(pageData.title, language)}

{resolveLanguageText(pageData.subtitle, language)}

``` **Input (dateien.ts):** ```typescript title: { de: 'Dateien', en: 'Files', fr: 'Fichiers' } ``` **Process:** ```typescript // pageInterface.ts Line 87-91 export const resolveLanguageText = (text: string | LanguageText, language: 'de') => { if (typeof text === 'string') return text; return text[language] || text.de || ''; }; ``` **Result:** `'Dateien'` βœ… #### 2. **Header Button Labels** (`PageRenderer.tsx` Line 230) ```typescript {button.icon && } {resolveLanguageText(button.label, language)} ``` **Input (dateien.ts):** ```typescript label: { de: 'Datei hochladen', en: 'Upload File', fr: 'TΓ©lΓ©charger un fichier' } ``` **Result:** `'Datei hochladen'` βœ… #### 3. **Simple Content Types** (heading, paragraph, list) All simple content types properly use `resolveLanguageText(content.content, language)` βœ… ### ~~❌ Where Language WAS NOT Resolved~~ NOW FIXED βœ… #### ~~1. **Table Column Labels**~~ FIXED βœ… **Problem:** ```typescript // PageRenderer.tsx Line 140 const columns = hookData.columns || configColumns; // Line 169 - Passed directly to FormGenerator ``` **Input (dateien.ts Lines 68-72):** ```typescript { key: 'file_name', label: { de: 'Dateiname', en: 'Filename', fr: 'Nom de fichier' }, // ... } ``` **What happens in FormGenerator:** ```typescript // FormGenerator.tsx Line 627 // Displays: [object Object] ❌ ``` **Expected:** ```typescript // Should be resolved BEFORE passing to FormGenerator const resolvedColumns = columns.map(col => ({ ...col, label: resolveLanguageText(col.label, language) })); ``` #### ~~2. **Action Button Titles**~~ FIXED βœ… **Problem:** ```typescript // PageRenderer.tsx Line 144-158 const formGeneratorActions = actionButtons?.map(action => { return { type: action.type, title: action.title, // ← LanguageText object NOT resolved! ❌ // ... }; }); ``` **Input (dateien.ts Lines 179-183):** ```typescript { type: 'view', title: { de: 'Datei vorschauen', en: 'Preview file', fr: 'AperΓ§u du fichier' }, // ... } ``` **What happens:** - FormGenerator passes raw `title` to action button components - Action buttons expect `title?: string` but receive `LanguageText` object - Tooltip/aria-label shows `[object Object]` ❌ **Expected:** ```typescript const formGeneratorActions = actionButtons?.map(action => { return { type: action.type, title: resolveLanguageText(action.title, language), // βœ… Resolve here! // ... }; }); ``` #### 3. **Filter Placeholders** (`FormGenerator.tsx` Line 642) ```typescript ``` If `column.label` is a LanguageText object, this breaks! ❌ --- ## βœ… Issues Fixed ### ~~Issue #1: Column Labels Not Resolved~~ FIXED βœ… **Location:** `PageRenderer.tsx` Line 142-146 **Fixed Code:** ```typescript const columns = hookData.columns || configColumns; // CRITICAL: Resolve LanguageText objects in column labels const resolvedColumns = columns.map(col => ({ ...col, label: resolveLanguageText(col.label, language) })); ``` ### ~~Issue #2: Action Button Titles Not Resolved~~ FIXED βœ… **Location:** `PageRenderer.tsx` Line 150-165 **Fixed Code:** ```typescript const formGeneratorActions = actionButtons?.map(action => { return { type: action.type, // CRITICAL: Resolve LanguageText objects in action titles title: resolveLanguageText(action.title, language), // βœ… Resolved string isProcessing: action.loading || (() => false), disabled: action.disabled || (() => false), // ... }; }); ``` **Result:** All LanguageText objects are now properly resolved to strings before being passed to FormGenerator! πŸŽ‰ --- ## πŸ“Š Data Flow Summary ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ dateien.ts Configuration β”‚ β”‚ - Page metadata (title, subtitle) β†’ LanguageText β”‚ β”‚ - Header buttons (labels) β†’ LanguageText β”‚ β”‚ - Table columns (labels) β†’ LanguageText ⚠️ β”‚ β”‚ - Action buttons (titles) β†’ LanguageText ⚠️ β”‚ β”‚ - Privilege checker β†’ viewerRole β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PageManager.tsx β”‚ β”‚ - Fetches page config β”‚ β”‚ - Checks privilege (async) βœ… β”‚ β”‚ - Gets current language from context βœ… β”‚ β”‚ - Passes both to PageRenderer β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PageRenderer.tsx β”‚ β”‚ - Resolves: title, subtitle, button labels βœ… β”‚ β”‚ - Does NOT resolve: column labels, action titles ❌ β”‚ β”‚ - Passes unresolved objects to FormGenerator β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ FormGenerator.tsx β”‚ β”‚ - Receives columns with LanguageText objects ❌ β”‚ β”‚ - Displays [object Object] for labels β”‚ β”‚ - Has access to useLanguage() but doesn't use it β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## βœ… Best Practices ### 1. **Privilege Checks** - βœ… Always check at page level (`pageData.privilegeChecker`) - βœ… Check at button level for sensitive actions - βœ… Checks are async - handled properly - βœ… Reads from `localStorage('currentUser').privilege` ### 2. **Language Resolution** - βœ… Get language from `useLanguage()` context - βœ… Resolve ALL LanguageText objects before passing to child components - βœ… Use `resolveLanguageText()` utility function - ❌ DON'T pass raw LanguageText objects to generic components ### 3. **Type Safety** ```typescript // ❌ Bad - allows LanguageText to leak through interface ActionButton { title?: string | LanguageText; // Ambiguous! } // βœ… Good - clearly separate config from resolved interface ActionButtonConfig { title: string | LanguageText; // Input config } interface ActionButtonProps { title?: string; // Resolved output } ``` --- ## βœ… Completed 1. ~~**Fix PageRenderer** to resolve column labels and action titles~~ βœ… DONE 2. **Add type checks** to ensure LanguageText resolution (optional enhancement) 3. **Update FormGenerator types** to strictly expect `string` for labels (optional enhancement) 4. **Add console warnings** when LanguageText objects are not resolved (optional enhancement) 5. **Test with all three languages** (de, en, fr) - Ready for testing! --- ## πŸ“ Key Files | File | Role | Line References | |------|------|-----------------| | `src/core/PageManager/data/pages/dateien.ts` | Page configuration | 65-124 (columns), 176-230 (actions), 243 (privilege) | | `src/core/PageManager/PageManager.tsx` | Page routing & privilege check | 67-78 (fetch & check), 20 (language), 103 (pass to renderer) | | `src/core/PageManager/PageRenderer.tsx` | Page rendering | 140 (columns), 144-158 (actions), 191-230 (header) | | `src/components/FormGenerator/FormGenerator.tsx` | Table rendering | 105 (useLanguage), 627, 642 (display labels) | | `src/utils/privilegeCheckers.ts` | Privilege checking | 4-21 (getCurrentUserPrivilege), 199-208 (viewerRole) | | `src/contexts/LanguageContext.tsx` | Language state | 46-57 (get from currentUser) | --- ## 🎯 Conclusion **Privilege checking works perfectly:** βœ… - Checks happen at the right time (before rendering) - Uses cached user data from localStorage - Async handling is correct - Multiple levels of checks (page, button, content) **Language resolution now works completely:** βœ… - βœ… Page headers, buttons, simple content - βœ… Table columns labels (FIXED!) - βœ… Action button titles (FIXED!) - All LanguageText objects are resolved before passing to FormGenerator