Compare commits
4 Commits
9ba7194d41
...
f9ad00a7e7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9ad00a7e7 | ||
|
|
aaeeee1717 | ||
|
|
b095d9de1c | ||
|
|
ae78b2d878 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lageplan",
|
"name": "lageplan",
|
||||||
"version": "1.8.2",
|
"version": "1.8.6",
|
||||||
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -57,33 +57,31 @@ export default function AdminPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="h-14 border-b border-border bg-card flex items-center justify-between px-6">
|
<header className="h-14 border-b border-border bg-card flex items-center justify-between px-3 md:px-6 gap-2">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-2 md:gap-4 min-w-0">
|
||||||
<Link href="/app">
|
<Link href="/app">
|
||||||
<Button variant="ghost" size="sm">
|
<Button variant="ghost" size="sm" className="px-2 shrink-0">
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<ArrowLeft className="w-4 h-4 md:mr-2" />
|
||||||
Zurück zur App
|
<span className="hidden sm:inline">Zurück zur App</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="h-6 w-px bg-border" />
|
<div className="hidden sm:block h-6 w-px bg-border" />
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<MapPin className="w-5 h-5 text-primary" />
|
<MapPin className="w-5 h-5 text-primary shrink-0" />
|
||||||
<h1 className="font-semibold text-lg">Administration</h1>
|
<h1 className="font-semibold text-base md:text-lg truncate">Administration</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-2 text-sm min-w-0 shrink-0">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<Shield className="w-4 h-4 text-primary shrink-0 hidden sm:block" />
|
||||||
<Shield className="w-4 h-4 text-primary" />
|
<span className="font-medium truncate max-w-[100px] md:max-w-none">{user.name}</span>
|
||||||
<span className="font-medium">{user.name}</span>
|
<span className="hidden sm:inline text-xs bg-primary/10 text-primary px-2 py-0.5 rounded-full">{user.role}</span>
|
||||||
<span className="text-xs bg-primary/10 text-primary px-2 py-0.5 rounded-full">{user.role}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="container mx-auto py-6 px-4 max-w-7xl">
|
<div className="container mx-auto py-6 px-4 max-w-7xl">
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
||||||
{isServerAdmin ? (
|
{isServerAdmin ? (
|
||||||
<TabsList className="grid w-full grid-cols-7 max-w-4xl">
|
<TabsList className="w-full max-w-4xl h-auto flex flex-nowrap overflow-x-auto justify-start md:grid md:grid-cols-7 [&>button]:shrink-0 [&>button]:whitespace-nowrap">
|
||||||
<TabsTrigger value="tenants" className="gap-2"><Shield className="w-4 h-4" />Mandanten</TabsTrigger>
|
<TabsTrigger value="tenants" className="gap-2"><Shield className="w-4 h-4" />Mandanten</TabsTrigger>
|
||||||
<TabsTrigger value="projects" className="gap-2"><Map className="w-4 h-4" />Einsätze</TabsTrigger>
|
<TabsTrigger value="projects" className="gap-2"><Map className="w-4 h-4" />Einsätze</TabsTrigger>
|
||||||
<TabsTrigger value="icons" className="gap-2"><ImageIcon className="w-4 h-4" />Symbole</TabsTrigger>
|
<TabsTrigger value="icons" className="gap-2"><ImageIcon className="w-4 h-4" />Symbole</TabsTrigger>
|
||||||
@@ -93,7 +91,7 @@ export default function AdminPage() {
|
|||||||
<TabsTrigger value="settings" className="gap-2"><Settings className="w-4 h-4" />System</TabsTrigger>
|
<TabsTrigger value="settings" className="gap-2"><Settings className="w-4 h-4" />System</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
) : (
|
) : (
|
||||||
<TabsList className="grid w-full grid-cols-7 max-w-4xl">
|
<TabsList className="w-full max-w-4xl h-auto flex flex-nowrap overflow-x-auto justify-start md:grid md:grid-cols-7 [&>button]:shrink-0 [&>button]:whitespace-nowrap">
|
||||||
<TabsTrigger value="org" className="gap-2"><Building2 className="w-4 h-4" />Organisation</TabsTrigger>
|
<TabsTrigger value="org" className="gap-2"><Building2 className="w-4 h-4" />Organisation</TabsTrigger>
|
||||||
<TabsTrigger value="users" className="gap-2"><Users className="w-4 h-4" />Benutzer</TabsTrigger>
|
<TabsTrigger value="users" className="gap-2"><Users className="w-4 h-4" />Benutzer</TabsTrigger>
|
||||||
<TabsTrigger value="icons" className="gap-2"><ImageIcon className="w-4 h-4" />Symbole</TabsTrigger>
|
<TabsTrigger value="icons" className="gap-2"><ImageIcon className="w-4 h-4" />Symbole</TabsTrigger>
|
||||||
|
|||||||
@@ -3,26 +3,45 @@ import { prisma } from '@/lib/db'
|
|||||||
import { getSession } from '@/lib/auth'
|
import { getSession } from '@/lib/auth'
|
||||||
import { normalizeModules, DEFAULT_MODULES } from '@/lib/modules'
|
import { normalizeModules, DEFAULT_MODULES } from '@/lib/modules'
|
||||||
|
|
||||||
/** Cockpit-Modul-Konfiguration des eigenen Mandanten lesen */
|
/**
|
||||||
export async function GET() {
|
* Ermittelt den massgeblichen Mandanten für die Modul-Verwaltung.
|
||||||
|
* SERVER_ADMIN darf einen beliebigen Mandanten (z.B. den des offenen Einsatzes) verwalten;
|
||||||
|
* andere nur ihren eigenen. `canManage` sagt, ob dieser Nutzer speichern darf.
|
||||||
|
*/
|
||||||
|
function resolveTenant(user: any, requestedTenantId: string | null): { tenantId: string | null; canManage: boolean } {
|
||||||
|
const isServerAdmin = user.role === 'SERVER_ADMIN'
|
||||||
|
const isTenantAdmin = user.role === 'TENANT_ADMIN'
|
||||||
|
if (isServerAdmin) {
|
||||||
|
// SERVER_ADMIN: nimmt den angefragten Mandanten (Einsatz), sonst den eigenen (falls vorhanden)
|
||||||
|
return { tenantId: requestedTenantId || user.tenantId || null, canManage: true }
|
||||||
|
}
|
||||||
|
// Alle anderen: immer nur der eigene Mandant. Admin dieses Mandanten darf verwalten.
|
||||||
|
return { tenantId: user.tenantId || null, canManage: isTenantAdmin && !!user.tenantId }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cockpit-Modul-Konfiguration lesen (Mandant = eigener oder, für SERVER_ADMIN, der des Einsatzes) */
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const user = await getSession()
|
const user = await getSession()
|
||||||
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
||||||
|
|
||||||
if (!user.tenantId) {
|
const requested = request.nextUrl.searchParams.get('tenantId')
|
||||||
// SERVER_ADMIN ohne Mandant: Standard-Cockpit
|
const { tenantId, canManage } = resolveTenant(user, requested)
|
||||||
|
|
||||||
|
if (!tenantId) {
|
||||||
|
// Kein Mandant auflösbar: Standard-Cockpit, nicht verwaltbar
|
||||||
return NextResponse.json({ modules: DEFAULT_MODULES, canManage: false })
|
return NextResponse.json({ modules: DEFAULT_MODULES, canManage: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
const tenant = await (prisma as any).tenant.findUnique({
|
const tenant = await (prisma as any).tenant.findUnique({
|
||||||
where: { id: user.tenantId },
|
where: { id: tenantId },
|
||||||
select: { modulesConfig: true },
|
select: { modulesConfig: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
const canManage = user.role === 'SERVER_ADMIN' || user.role === 'TENANT_ADMIN'
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
modules: normalizeModules(tenant?.modulesConfig),
|
modules: normalizeModules(tenant?.modulesConfig),
|
||||||
canManage,
|
canManage,
|
||||||
|
tenantId,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching module config:', error)
|
console.error('Error fetching module config:', error)
|
||||||
@@ -30,22 +49,23 @@ export async function GET() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cockpit-Modul-Konfiguration speichern (nur Admins) */
|
/** Cockpit-Modul-Konfiguration speichern (nur Admins des betroffenen Mandanten) */
|
||||||
export async function PUT(request: NextRequest) {
|
export async function PUT(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const user = await getSession()
|
const user = await getSession()
|
||||||
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
if (!user) return NextResponse.json({ error: 'Nicht autorisiert' }, { status: 401 })
|
||||||
if (!user.tenantId) return NextResponse.json({ error: 'Kein Mandant zugeordnet' }, { status: 400 })
|
|
||||||
if (user.role !== 'SERVER_ADMIN' && user.role !== 'TENANT_ADMIN') {
|
|
||||||
return NextResponse.json({ error: 'Keine Berechtigung' }, { status: 403 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
|
const { tenantId, canManage } = resolveTenant(user, body?.tenantId || null)
|
||||||
|
|
||||||
|
if (!tenantId) return NextResponse.json({ error: 'Kein Mandant zugeordnet' }, { status: 400 })
|
||||||
|
if (!canManage) return NextResponse.json({ error: 'Keine Berechtigung' }, { status: 403 })
|
||||||
|
|
||||||
// normalizeModules verwirft Unbekanntes und stellt eingebaute Module sicher
|
// normalizeModules verwirft Unbekanntes und stellt eingebaute Module sicher
|
||||||
const modules = normalizeModules(body?.modules)
|
const modules = normalizeModules(body?.modules)
|
||||||
|
|
||||||
await (prisma as any).tenant.update({
|
await (prisma as any).tenant.update({
|
||||||
where: { id: user.tenantId },
|
where: { id: tenantId },
|
||||||
data: { modulesConfig: modules },
|
data: { modulesConfig: modules },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1165,6 +1165,7 @@ export default function AppPage() {
|
|||||||
einsatzNr={(currentProject as any)?.einsatzNr || ''}
|
einsatzNr={(currentProject as any)?.einsatzNr || ''}
|
||||||
canEdit={canEditJournal}
|
canEdit={canEditJournal}
|
||||||
tenantId={tenant?.id || null}
|
tenantId={tenant?.id || null}
|
||||||
|
projectTenantId={(currentProject as any)?.tenantId ?? null}
|
||||||
tenantName={tenant?.name || ''}
|
tenantName={tenant?.name || ''}
|
||||||
tenantLogoUrl={tenant?.id ? `/api/admin/tenants/${tenant.id}/logo/serve` : null}
|
tenantLogoUrl={tenant?.id ? `/api/admin/tenants/${tenant.id}/logo/serve` : null}
|
||||||
mapRef={mapRef}
|
mapRef={mapRef}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ interface JournalViewProps {
|
|||||||
journalfuehrer: string
|
journalfuehrer: string
|
||||||
canEdit: boolean
|
canEdit: boolean
|
||||||
tenantId?: string | null
|
tenantId?: string | null
|
||||||
|
/** Mandant des OFFENEN Einsatzes (kann von dem des Nutzers abweichen — z.B. SERVER_ADMIN). */
|
||||||
|
projectTenantId?: string | null
|
||||||
einsatzNr?: string
|
einsatzNr?: string
|
||||||
tenantName?: string
|
tenantName?: string
|
||||||
tenantLogoUrl?: string | null
|
tenantLogoUrl?: string | null
|
||||||
@@ -67,7 +69,7 @@ function ModuleCard({ icon, name, count, children }: {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function JournalView({ projectId, projectTitle, projectLocation, mode, einsatzleiter, journalfuehrer, canEdit, tenantId, einsatzNr, tenantName, tenantLogoUrl, mapRef, mapScreenshot: preCapuredScreenshot }: JournalViewProps) {
|
export function JournalView({ projectId, projectTitle, projectLocation, mode, einsatzleiter, journalfuehrer, canEdit, tenantId, projectTenantId, einsatzNr, tenantName, tenantLogoUrl, mapRef, mapScreenshot: preCapuredScreenshot }: JournalViewProps) {
|
||||||
const isUebung = mode === 'UEBUNG'
|
const isUebung = mode === 'UEBUNG'
|
||||||
const [entries, setEntries] = useState<JournalEntry[]>([])
|
const [entries, setEntries] = useState<JournalEntry[]>([])
|
||||||
const [checkItems, setCheckItems] = useState<JournalCheckItem[]>([])
|
const [checkItems, setCheckItems] = useState<JournalCheckItem[]>([])
|
||||||
@@ -145,16 +147,18 @@ export function JournalView({ projectId, projectTitle, projectLocation, mode, ei
|
|||||||
}
|
}
|
||||||
}, [projectId])
|
}, [projectId])
|
||||||
|
|
||||||
// Cockpit-Modul-Konfiguration laden
|
// Cockpit-Modul-Konfiguration laden — Mandant des OFFENEN Einsatzes (Fallback: eigener)
|
||||||
|
const modulesTenantId = projectTenantId || tenantId || null
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('/api/tenant/modules')
|
const url = modulesTenantId ? `/api/tenant/modules?tenantId=${encodeURIComponent(modulesTenantId)}` : '/api/tenant/modules'
|
||||||
|
fetch(url)
|
||||||
.then(r => r.ok ? r.json() : null)
|
.then(r => r.ok ? r.json() : null)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data?.modules) setModules(data.modules)
|
if (data?.modules) setModules(data.modules)
|
||||||
if (data) setCanManageModules(!!data.canManage)
|
if (data) setCanManageModules(!!data.canManage)
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
}, [tenantId])
|
}, [modulesTenantId])
|
||||||
|
|
||||||
// Init check items from templates if none exist (guarded against double-call)
|
// Init check items from templates if none exist (guarded against double-call)
|
||||||
const initCheckItems = useCallback(async () => {
|
const initCheckItems = useCallback(async () => {
|
||||||
@@ -516,16 +520,16 @@ export function JournalView({ projectId, projectTitle, projectLocation, mode, ei
|
|||||||
<div className="h-full overflow-auto bg-stone-50 dark:bg-background print:bg-white print:overflow-visible">
|
<div className="h-full overflow-auto bg-stone-50 dark:bg-background print:bg-white print:overflow-visible">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="p-3 md:p-4 border-b-2 border-red-600 dark:border-red-800 bg-white dark:bg-card shadow-sm print:bg-white print:p-2 print:border-b-2 print:border-red-600">
|
<div className="p-3 md:p-4 border-b-2 border-red-600 dark:border-red-800 bg-white dark:bg-card shadow-sm print:bg-white print:p-2 print:border-b-2 print:border-red-600">
|
||||||
<div className="flex items-center justify-between mb-2 print:mb-1">
|
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-2 mb-2 print:mb-1">
|
||||||
<h2 className="text-lg md:text-xl font-bold flex items-center gap-2 print:text-base text-red-800 dark:text-red-400">
|
<h2 className="text-lg md:text-xl font-bold flex items-center gap-2 print:text-base text-red-800 dark:text-red-400">
|
||||||
<ClipboardList className="w-5 h-5 md:w-6 md:h-6 print:w-4 print:h-4" />
|
<ClipboardList className="w-5 h-5 md:w-6 md:h-6 print:w-4 print:h-4" />
|
||||||
{isUebung ? 'Übungs-Journal' : 'Einsatz-Journal'}
|
{isUebung ? 'Übungs-Journal' : 'Einsatz-Journal'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex gap-1.5 print:hidden">
|
<div className="flex flex-wrap gap-1.5 print:hidden">
|
||||||
{canManageModules && (
|
{canManageModules && (
|
||||||
<Button variant="outline" size="sm" onClick={() => setShowModuleManager(true)} title="Cockpit-Module verwalten">
|
<Button variant="outline" size="sm" onClick={() => setShowModuleManager(true)} title="Cockpit-Module verwalten (Spalten, eigene Module)">
|
||||||
<Blocks className="w-4 h-4 md:mr-1.5" />
|
<Blocks className="w-4 h-4 mr-1.5" />
|
||||||
<span className="hidden md:inline">Module</span>
|
Module
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
@@ -847,6 +851,7 @@ export function JournalView({ projectId, projectTitle, projectLocation, mode, ei
|
|||||||
open={showModuleManager}
|
open={showModuleManager}
|
||||||
onOpenChange={setShowModuleManager}
|
onOpenChange={setShowModuleManager}
|
||||||
modules={modules}
|
modules={modules}
|
||||||
|
tenantId={modulesTenantId}
|
||||||
onSaved={setModules}
|
onSaved={setModules}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ interface ModuleManagerDialogProps {
|
|||||||
open: boolean
|
open: boolean
|
||||||
onOpenChange: (open: boolean) => void
|
onOpenChange: (open: boolean) => void
|
||||||
modules: CockpitModule[]
|
modules: CockpitModule[]
|
||||||
|
/** Mandant, dessen Module gespeichert werden (Einsatz-Mandant; für SERVER_ADMIN relevant). */
|
||||||
|
tenantId?: string | null
|
||||||
onSaved: (modules: CockpitModule[]) => void
|
onSaved: (modules: CockpitModule[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ interface ModuleManagerDialogProps {
|
|||||||
* aus Vorlagen erstellen (Checkliste, Atemschutz, Kräfte, Lagemeldungen, leer).
|
* aus Vorlagen erstellen (Checkliste, Atemschutz, Kräfte, Lagemeldungen, leer).
|
||||||
* Gespeichert wird pro Mandant (gilt für alle Einsätze).
|
* Gespeichert wird pro Mandant (gilt für alle Einsätze).
|
||||||
*/
|
*/
|
||||||
export function ModuleManagerDialog({ open, onOpenChange, modules, onSaved }: ModuleManagerDialogProps) {
|
export function ModuleManagerDialog({ open, onOpenChange, modules, tenantId, onSaved }: ModuleManagerDialogProps) {
|
||||||
const [working, setWorking] = useState<CockpitModule[]>(modules)
|
const [working, setWorking] = useState<CockpitModule[]>(modules)
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
@@ -133,7 +135,7 @@ export function ModuleManagerDialog({ open, onOpenChange, modules, onSaved }: Mo
|
|||||||
const res = await fetch('/api/tenant/modules', {
|
const res = await fetch('/api/tenant/modules', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ modules: working }),
|
body: JSON.stringify({ modules: working, tenantId: tenantId || undefined }),
|
||||||
})
|
})
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (!res.ok) throw new Error(data.error || 'Speichern fehlgeschlagen')
|
if (!res.ok) throw new Error(data.error || 'Speichern fehlgeschlagen')
|
||||||
|
|||||||
@@ -370,6 +370,14 @@ export function Topbar({
|
|||||||
<Key className="w-4 h-4 mr-2" />
|
<Key className="w-4 h-4 mr-2" />
|
||||||
Kennwort ändern
|
Kennwort ändern
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => window.location.href = '/settings'}>
|
||||||
|
<Lock className="w-4 h-4 mr-2" />
|
||||||
|
Sicherheit & 2FA
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => window.location.href = '/konto/datenschutz'}>
|
||||||
|
<Shield className="w-4 h-4 mr-2" />
|
||||||
|
Konto & Datenschutz
|
||||||
|
</DropdownMenuItem>
|
||||||
{(userRole === 'SERVER_ADMIN' || userRole === 'TENANT_ADMIN') && (
|
{(userRole === 'SERVER_ADMIN' || userRole === 'TENANT_ADMIN') && (
|
||||||
<DropdownMenuItem onClick={() => window.location.href = '/admin'}>
|
<DropdownMenuItem onClick={() => window.location.href = '/admin'}>
|
||||||
<Shield className="w-4 h-4 mr-2" />
|
<Shield className="w-4 h-4 mr-2" />
|
||||||
@@ -492,6 +500,14 @@ export function Topbar({
|
|||||||
<Key className="w-4 h-4 mr-2" />
|
<Key className="w-4 h-4 mr-2" />
|
||||||
Kennwort ändern
|
Kennwort ändern
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => window.location.href = '/settings'}>
|
||||||
|
<Lock className="w-4 h-4 mr-2" />
|
||||||
|
Sicherheit & 2FA
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => window.location.href = '/konto/datenschutz'}>
|
||||||
|
<Shield className="w-4 h-4 mr-2" />
|
||||||
|
Konto & Datenschutz
|
||||||
|
</DropdownMenuItem>
|
||||||
{onLogout && (
|
{onLogout && (
|
||||||
<DropdownMenuItem onClick={onLogout} className="text-destructive focus:text-destructive">
|
<DropdownMenuItem onClick={onLogout} className="text-destructive focus:text-destructive">
|
||||||
<LogOut className="w-4 h-4 mr-2" />
|
<LogOut className="w-4 h-4 mr-2" />
|
||||||
|
|||||||
Reference in New Issue
Block a user