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:
+21
-11
@@ -1,4 +1,5 @@
|
||||
import { Resend } from "resend";
|
||||
import { getSetting } from "./settings";
|
||||
|
||||
function getResend() {
|
||||
return new Resend(process.env.RESEND_API_KEY);
|
||||
@@ -6,9 +7,13 @@ function getResend() {
|
||||
const FROM = process.env.EMAIL_FROM ?? "noreply@mailsend.second-brain.ru";
|
||||
const BASE_URL = process.env.BETTER_AUTH_URL ?? "https://school.second-brain.ru";
|
||||
|
||||
async function getSchoolName() {
|
||||
return getSetting("schoolName");
|
||||
}
|
||||
|
||||
// ── HTML template (inline styles for maximum email client compatibility) ───────
|
||||
|
||||
function base(content: string) {
|
||||
function base(content: string, schoolName = "Second Brain") {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="ru" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
@@ -36,7 +41,7 @@ function base(content: string) {
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="padding:20px 28px;border-bottom:2px solid #AAAAAA;background-color:#F5F5F0;">
|
||||
<p style="margin:0;font-family:Arial,Helvetica,sans-serif;font-size:13px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:#323232;">Second Brain · Образовательная платформа</p>
|
||||
<p style="margin:0;font-family:Arial,Helvetica,sans-serif;font-size:13px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:#323232;">${schoolName} · Образовательная платформа</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -97,6 +102,7 @@ function quote(text: string) {
|
||||
// ── Email senders ─────────────────────────────────────────────────────────────
|
||||
|
||||
export async function sendCourseAccessEmail(to: string, name: string, courseTitle: string) {
|
||||
const school = await getSchoolName();
|
||||
await getResend().emails.send({
|
||||
from: FROM,
|
||||
to,
|
||||
@@ -106,7 +112,7 @@ export async function sendCourseAccessEmail(to: string, name: string, courseTitl
|
||||
<p ${p}>Вам открыт доступ к курсу <strong>«${courseTitle}»</strong>.</p>
|
||||
<p ${pLast}>Перейдите на платформу чтобы начать обучение:</p>
|
||||
${btn(`${BASE_URL}/dashboard`, "Перейти к курсам")}
|
||||
`),
|
||||
`, school),
|
||||
}).catch((e) => console.error("[email] sendCourseAccessEmail:", e));
|
||||
}
|
||||
|
||||
@@ -117,6 +123,7 @@ export async function sendHomeworkSubmittedEmail(
|
||||
lessonTitle: string,
|
||||
submissionId: string
|
||||
) {
|
||||
const school = await getSchoolName();
|
||||
await getResend().emails.send({
|
||||
from: FROM,
|
||||
to,
|
||||
@@ -126,7 +133,7 @@ export async function sendHomeworkSubmittedEmail(
|
||||
<p ${p}>Студент <strong>${studentName}</strong> сдал работу по уроку <strong>«${lessonTitle}»</strong>.</p>
|
||||
<p ${pLast}>Откройте работу чтобы проверить и оставить фидбек:</p>
|
||||
${btn(`${BASE_URL}/curator/homework/${submissionId}`, "Проверить работу")}
|
||||
`),
|
||||
`, school),
|
||||
}).catch((e) => console.error("[email] sendHomeworkSubmittedEmail:", e));
|
||||
}
|
||||
|
||||
@@ -137,6 +144,7 @@ export async function sendFeedbackReceivedEmail(
|
||||
feedbackText: string,
|
||||
lessonUrl: string
|
||||
) {
|
||||
const school = await getSchoolName();
|
||||
await getResend().emails.send({
|
||||
from: FROM,
|
||||
to,
|
||||
@@ -147,35 +155,37 @@ export async function sendFeedbackReceivedEmail(
|
||||
${quote(feedbackText)}
|
||||
<p ${pLast}>Вернитесь к уроку чтобы увидеть полный фидбек:</p>
|
||||
${btn(lessonUrl, "Открыть урок")}
|
||||
`),
|
||||
`, school),
|
||||
}).catch((e) => console.error("[email] sendFeedbackReceivedEmail:", e));
|
||||
}
|
||||
|
||||
export async function sendWelcomeEmail(to: string, name: string) {
|
||||
const school = await getSchoolName();
|
||||
await getResend().emails.send({
|
||||
from: FROM,
|
||||
to,
|
||||
subject: "Добро пожаловать в Second Brain",
|
||||
subject: `Добро пожаловать в ${school}`,
|
||||
html: base(`
|
||||
<p ${p}>Привет, ${name}!</p>
|
||||
<p ${p}>Ваш аккаунт на образовательной платформе <strong>Second Brain</strong> подтверждён.</p>
|
||||
<p ${p}>Ваш аккаунт на образовательной платформе <strong>${school}</strong> подтверждён.</p>
|
||||
<p ${pLast}>После того как администратор откроет вам доступ к курсу, вы получите письмо и сможете начать обучение.</p>
|
||||
${btn(`${BASE_URL}/dashboard`, "Перейти на платформу")}
|
||||
`),
|
||||
`, school),
|
||||
}).catch((e) => console.error("[email] sendWelcomeEmail:", e));
|
||||
}
|
||||
|
||||
export async function sendTestEmail(to: string) {
|
||||
const school = await getSchoolName();
|
||||
await getResend().emails.send({
|
||||
from: FROM,
|
||||
to,
|
||||
subject: "Тест — Second Brain LMS",
|
||||
subject: `Тест — ${school} LMS`,
|
||||
html: base(`
|
||||
<p ${p}>Привет!</p>
|
||||
<p ${p}>Это тестовое письмо от платформы <strong>Second Brain LMS</strong>.</p>
|
||||
<p ${p}>Это тестовое письмо от платформы <strong>${school} LMS</strong>.</p>
|
||||
<p ${p}>Так будут выглядеть все уведомления — доступ к курсу, фидбек по ДЗ и другие.</p>
|
||||
<p ${pLast}>Если письмо отображается корректно, значит email-уведомления настроены и работают.</p>
|
||||
${btn(`${BASE_URL}/dashboard`, "Открыть платформу")}
|
||||
`),
|
||||
`, school),
|
||||
}).catch((e) => console.error("[email] sendTestEmail:", e));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user