diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx
index cf58465..1686908 100644
--- a/src/app/(auth)/login/page.tsx
+++ b/src/app/(auth)/login/page.tsx
@@ -1,17 +1,35 @@
+import { getSetting } from "@/lib/settings";
import { LoginForm } from "./login-form";
-export default function LoginPage() {
+export default async function LoginPage({
+ searchParams,
+}: {
+ searchParams: Promise<{ notice?: string }>;
+}) {
+ const [schoolName, { notice }] = await Promise.all([
+ getSetting("schoolName"),
+ searchParams,
+ ]);
+
return (
- Second Brain
+ {schoolName}
Образовательная платформа
+ {notice === "registration_closed" && (
+
+ Регистрация временно закрыта. Обратитесь к администратору.
+
+ )}
diff --git a/src/app/(auth)/register/page.tsx b/src/app/(auth)/register/page.tsx
index 3bdf363..c4e9088 100644
--- a/src/app/(auth)/register/page.tsx
+++ b/src/app/(auth)/register/page.tsx
@@ -1,19 +1,32 @@
+import { redirect } from "next/navigation";
+import { getSettings } from "@/lib/settings";
import { RegisterForm } from "./register-form";
-export default function RegisterPage() {
+export default async function RegisterPage() {
+ const settings = await getSettings();
+
+ if (settings.registrationEnabled !== "true") {
+ redirect("/login?notice=registration_closed");
+ }
+
return (
- Second Brain
+ {settings.schoolName}
Образовательная платформа
-
+
diff --git a/src/app/(auth)/register/register-form.tsx b/src/app/(auth)/register/register-form.tsx
index 31365ed..a96cb09 100644
--- a/src/app/(auth)/register/register-form.tsx
+++ b/src/app/(auth)/register/register-form.tsx
@@ -4,10 +4,18 @@ import { useState } from "react";
import Link from "next/link";
import { signUp } from "@/lib/auth-client";
-export function RegisterForm() {
+interface Props {
+ showTermsCheckbox: boolean;
+ privacyPolicyUrl: string;
+ termsUrl: string;
+ offerUrl: string;
+}
+
+export function RegisterForm({ showTermsCheckbox, privacyPolicyUrl, termsUrl, offerUrl }: Props) {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
+ const [termsAccepted, setTermsAccepted] = useState(false);
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
@@ -22,8 +30,18 @@ export function RegisterForm() {
fontFamily: "inherit",
} as React.CSSProperties;
+ const legalLinks = [
+ { url: privacyPolicyUrl, label: "Политику конфиденциальности" },
+ { url: termsUrl, label: "Согласие на обработку данных" },
+ { url: offerUrl, label: "Договор-оферту" },
+ ].filter((l) => l.url);
+
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
+ if (showTermsCheckbox && !termsAccepted) {
+ setError("Необходимо принять условия для продолжения");
+ return;
+ }
setError("");
setLoading(true);
@@ -102,6 +120,38 @@ export function RegisterForm() {
placeholder="Минимум 8 символов"
/>
+
+ {showTermsCheckbox && (
+
+ )}
+
{error && (
{error}
diff --git a/src/app/(student)/layout.tsx b/src/app/(student)/layout.tsx
index 4860840..52aa8c7 100644
--- a/src/app/(student)/layout.tsx
+++ b/src/app/(student)/layout.tsx
@@ -16,6 +16,17 @@ export default async function StudentLayout({ children }: { children: React.Reac
if (maintenance === "true") redirect("/maintenance");
}
+ const [schoolName, logoUrl, showLogo, socialYoutube, socialVk, socialTelegram, orgRequisites] =
+ await Promise.all([
+ getSetting("schoolName"),
+ getSetting("logoUrl"),
+ getSetting("showLogo"),
+ getSetting("socialYoutube"),
+ getSetting("socialVk"),
+ getSetting("socialTelegram"),
+ getSetting("orgRequisites"),
+ ]);
+
const isImpersonating = !!(session.session as { impersonatedBy?: string }).impersonatedBy;
return (
@@ -25,8 +36,12 @@ export default async function StudentLayout({ children }: { children: React.Reac
className="sticky top-0 z-10 flex items-center justify-between px-6 py-3"
style={{ borderBottom: "2px solid var(--border)", backgroundColor: "var(--background)" }}
>
-
- Second Brain
+
+ {logoUrl && showLogo === "true" && (
+ // eslint-disable-next-line @next/next/no-img-element
+
+ )}
+ {schoolName}
{session.user.name}
@@ -34,6 +49,35 @@ export default async function StudentLayout({ children }: { children: React.Reac
{children}
+ {(socialYoutube || socialVk || socialTelegram || orgRequisites) && (
+
+ )}
);
}
diff --git a/src/app/curator/homework/[submissionId]/actions.ts b/src/app/curator/homework/[submissionId]/actions.ts
index a305e14..cb2dfe2 100644
--- a/src/app/curator/homework/[submissionId]/actions.ts
+++ b/src/app/curator/homework/[submissionId]/actions.ts
@@ -5,6 +5,7 @@ import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { revalidatePath } from "next/cache";
import { sendFeedbackReceivedEmail } from "@/lib/email";
+import { getSetting, asBool } from "@/lib/settings";
async function requireCurator() {
const session = await auth.api.getSession({ headers: await headers() });
@@ -78,14 +79,17 @@ export async function submitFeedback(
});
const { lesson } = submission.homework;
- const lessonUrl = `${process.env.BETTER_AUTH_URL ?? "https://school.second-brain.ru"}/courses/${lesson.module.course.slug}/lessons/${lesson.id}`;
- await sendFeedbackReceivedEmail(
- submission.user.email,
- submission.user.name,
- lesson.title,
- data.text,
- lessonUrl
- );
+ const notifySetting = await getSetting("notifyStudentOnFeedback");
+ if (asBool(notifySetting)) {
+ const lessonUrl = `${process.env.BETTER_AUTH_URL ?? "https://school.second-brain.ru"}/courses/${lesson.module.course.slug}/lessons/${lesson.id}`;
+ await sendFeedbackReceivedEmail(
+ submission.user.email,
+ submission.user.name,
+ lesson.title,
+ data.text,
+ lessonUrl
+ );
+ }
revalidatePath("/curator/homework");
revalidatePath(`/curator/homework/${submissionId}`);
diff --git a/src/components/admin/settings-form.tsx b/src/components/admin/settings-form.tsx
index 7eb115e..b69b1db 100644
--- a/src/components/admin/settings-form.tsx
+++ b/src/components/admin/settings-form.tsx
@@ -432,6 +432,62 @@ export function SettingsForm({ initial }: { initial: Settings }) {
+ {/* ── 7. Логотип ── */}
+
+
+ {/* ── 8. Социальные сети ── */}
+
+
{/* Bottom save button */}