121 lines
4.5 KiB
TypeScript
121 lines
4.5 KiB
TypeScript
import { Sidebar } from '@/components/layout/Sidebar'
|
|
import { Header } from '@/components/layout/Header'
|
|
import { auth, getSanitizedHeaders } from '@/lib/auth'
|
|
import { headers } from 'next/headers'
|
|
import { redirect } from 'next/navigation'
|
|
import { prisma } from '@innungsapp/shared'
|
|
import { ForcePasswordChange } from './ForcePasswordChange'
|
|
|
|
export default async function DashboardLayout({
|
|
children,
|
|
params,
|
|
}: {
|
|
children: React.ReactNode
|
|
params: Promise<{ slug: string }>
|
|
}) {
|
|
const sanitizedHeaders = await getSanitizedHeaders()
|
|
const session = await auth.api.getSession({ headers: sanitizedHeaders })
|
|
if (!session?.user) {
|
|
redirect('/login')
|
|
}
|
|
|
|
// Superadmin Redirect
|
|
const superAdminEmail = process.env.SUPERADMIN_EMAIL || 'superadmin@innungsapp.de'
|
|
if (session.user.email === superAdminEmail) {
|
|
redirect('/superadmin')
|
|
}
|
|
|
|
const { slug } = await params
|
|
const org = await prisma.organization.findUnique({
|
|
where: { slug }
|
|
})
|
|
|
|
// Basic security: Check if the user is an admin of this organization
|
|
const userRole = org
|
|
? await prisma.userRole.findUnique({
|
|
where: { orgId_userId: { orgId: org.id, userId: session.user.id } }
|
|
})
|
|
: null
|
|
|
|
// If not found for this slug, check if user is admin of ANY org and redirect there
|
|
if (!userRole || userRole.role !== 'admin') {
|
|
const anyAdminRole = await prisma.userRole.findFirst({
|
|
where: { userId: session.user.id, role: 'admin' },
|
|
include: { org: true },
|
|
orderBy: { createdAt: 'asc' },
|
|
})
|
|
console.error('[Dashboard] Zugriff verweigert Debug:', {
|
|
sessionUserId: session.user.id,
|
|
sessionUserEmail: session.user.email,
|
|
slug,
|
|
orgFound: !!org,
|
|
orgId: org?.id,
|
|
userRoleFound: !!userRole,
|
|
userRoleRole: userRole?.role,
|
|
anyAdminRoleFound: !!anyAdminRole,
|
|
anyAdminRoleOrgSlug: anyAdminRole?.org?.slug,
|
|
})
|
|
if (anyAdminRole?.org?.slug && anyAdminRole.org.slug !== slug) {
|
|
redirect(`/${anyAdminRole.org.slug}/dashboard`)
|
|
}
|
|
}
|
|
|
|
// ONLY admins are allowed in the administrative portal
|
|
if (!userRole || userRole.role !== 'admin') {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center p-4">
|
|
<div className="bg-white border rounded-xl p-8 max-w-md w-full text-center shadow-sm">
|
|
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-red-100 text-red-600">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m0-10.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.75c0 5.592 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.57-.598-3.75h-.152c-3.196 0-6.1-1.248-8.25-3.286Zm0 13.036h.008v.008H12v-.008Z" />
|
|
</svg>
|
|
</div>
|
|
<h1 className="text-xl font-bold text-gray-900 mb-2">Zugriff verweigert</h1>
|
|
<p className="text-gray-500 mb-6 text-sm">
|
|
Dieses Portal ist ausschließlich für Administratoren reserviert. Ihr Account verfügt nicht über die notwendigen Berechtigungen für diesen Bereich.
|
|
</p>
|
|
<form action={async () => {
|
|
'use server'
|
|
const { auth } = await import('@/lib/auth')
|
|
const { headers } = await import('next/headers')
|
|
await auth.api.signOut({ headers: await headers() })
|
|
redirect('/login')
|
|
}}>
|
|
<button type="submit" className="text-sm font-medium text-brand-600 hover:text-brand-700">
|
|
Abmelden und mit anderem Konto anmelden
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Force Password Change Check
|
|
// @ts-ignore - mustChangePassword is added via additionalFields
|
|
if (session.user.mustChangePassword) {
|
|
return (
|
|
<div className="min-h-screen overflow-y-auto bg-gray-50 flex flex-col items-center justify-center p-4">
|
|
<ForcePasswordChange slug={slug} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Inject Primary Color Theme
|
|
const primaryColor = org?.primaryColor || '#E63946'
|
|
|
|
return (
|
|
<div className="flex h-screen bg-gray-50">
|
|
<style>{`
|
|
:root {
|
|
--color-brand-primary: ${primaryColor};
|
|
}
|
|
`}</style>
|
|
<Sidebar orgName={org?.name} logoUrl={org?.logoUrl} />
|
|
<div className="flex-1 flex flex-col min-w-0">
|
|
<Header />
|
|
<main className="flex-1 overflow-y-auto p-6">{children}</main>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|