Add platform settings (Stage 9)

- Settings key-value table in Prisma with migration
- getSettings() / getSetting() helpers in lib/settings.ts
- Admin UI at /admin/settings with 6 sections: General, Notifications,
  Student profile, Legal docs, Curator permissions, Code injection
- saveSettings() server action with admin-only guard
- Maintenance mode: non-admin users redirected to /maintenance page
- schoolName propagated to page metadata and all email templates
- headCode / bodyCode injected into root layout <head> and <body>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 11:18:37 +05:00
parent 093e403f5f
commit e77588deb8
14 changed files with 834 additions and 29 deletions
+21 -6
View File
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Fira_Mono } from "next/font/google";
import "./globals.css";
import { getSettings } from "@/lib/settings";
const firaMono = Fira_Mono({
weight: ["400", "500", "700"],
@@ -9,19 +10,33 @@ const firaMono = Fira_Mono({
display: "swap",
});
export const metadata: Metadata = {
title: "Second Brain — Обучение",
description: "Образовательная платформа Second Brain",
};
export async function generateMetadata(): Promise<Metadata> {
const settings = await getSettings();
return {
title: `${settings.schoolName} — Обучение`,
description: settings.schoolDescription || "Образовательная платформа",
keywords: settings.schoolKeywords || undefined,
};
}
export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const settings = await getSettings();
return (
<html lang="ru" className={`${firaMono.variable} h-full antialiased`}>
<body className="min-h-full flex flex-col">{children}</body>
{settings.headCode ? (
<head dangerouslySetInnerHTML={{ __html: settings.headCode }} />
) : null}
<body className="min-h-full flex flex-col">
{children}
{settings.bodyCode ? (
<div dangerouslySetInnerHTML={{ __html: settings.bodyCode }} />
) : null}
</body>
</html>
);
}