frontend_nyla/docs/PRIVILEGE_AND_LANGUAGE_FLOW_DETAILED.md

581 lines
26 KiB
Markdown

# 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<boolean> => {
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)
<h1>{resolveLanguageText(pageData.title, language)}</h1>
<p>{resolveLanguageText(pageData.subtitle, language)}</p>
```
**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 && <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
<FormGenerator
columns={columns} // ← LanguageText objects NOT resolved! ❌
...
/>
```
**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
<label>{column.label}</label>
// 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
<label>
{t('formgen.filter.placeholder').replace('{column}', column.label)}
</label>
```
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)
}));
<FormGenerator
columns={resolvedColumns} // ✅ Resolved strings
...
/>
```
### ~~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