stadtwerke/innungsapp/apps/admin/app/[slug]/dashboard/layout.tsx

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 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>
)
}