Implement platform settings (Stage 9)
- Wire settings to actual platform behavior: maintenance mode, registration toggle, notification emails, curator feedback emails, email verification flag - Add logo (logoUrl, showLogo) and social network links (YouTube, VK, Telegram) settings - Show logo + school name dynamically in student layout header - Add footer to student layout with org requisites and social links - Register page: read settings server-side, validate terms checkbox with legal links - Login page: show notice when redirected from closed registration - Settings form: add Logo and Social Networks sections Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 (
|
||||
<div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: "var(--background)" }}>
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-2xl font-bold tracking-wide" style={{ color: "var(--foreground)" }}>
|
||||
Second Brain
|
||||
{schoolName}
|
||||
</h1>
|
||||
<p className="mt-1 text-sm uppercase tracking-widest" style={{ color: "var(--muted-foreground)", fontSize: "0.65rem" }}>
|
||||
Образовательная платформа
|
||||
</p>
|
||||
</div>
|
||||
{notice === "registration_closed" && (
|
||||
<div
|
||||
className="mb-4 px-4 py-3 text-sm text-center"
|
||||
style={{ border: "2px solid var(--border)", color: "var(--muted-foreground)" }}
|
||||
>
|
||||
Регистрация временно закрыта. Обратитесь к администратору.
|
||||
</div>
|
||||
)}
|
||||
<div className="card-aubade p-8">
|
||||
<LoginForm />
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: "var(--background)" }}>
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-2xl font-bold tracking-wide" style={{ color: "var(--foreground)" }}>
|
||||
Second Brain
|
||||
{settings.schoolName}
|
||||
</h1>
|
||||
<p className="mt-1 text-sm uppercase tracking-widest" style={{ color: "var(--muted-foreground)", fontSize: "0.65rem" }}>
|
||||
Образовательная платформа
|
||||
</p>
|
||||
</div>
|
||||
<div className="card-aubade p-8">
|
||||
<RegisterForm />
|
||||
<RegisterForm
|
||||
showTermsCheckbox={settings.showTermsCheckbox === "true"}
|
||||
privacyPolicyUrl={settings.privacyPolicyUrl}
|
||||
termsUrl={settings.termsUrl}
|
||||
offerUrl={settings.offerUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 символов"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showTermsCheckbox && (
|
||||
<label className="flex items-start gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={termsAccepted}
|
||||
onChange={(e) => setTermsAccepted(e.target.checked)}
|
||||
className="mt-0.5 flex-shrink-0"
|
||||
style={{ width: "16px", height: "16px", accentColor: "var(--foreground)" }}
|
||||
/>
|
||||
<span className="text-xs" style={{ color: "var(--muted-foreground)" }}>
|
||||
Я принимаю{" "}
|
||||
{legalLinks.length > 0
|
||||
? legalLinks.map((l, i) => (
|
||||
<span key={l.url}>
|
||||
<a
|
||||
href={l.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
style={{ color: "var(--foreground)" }}
|
||||
>
|
||||
{l.label}
|
||||
</a>
|
||||
{i < legalLinks.length - 1 ? ", " : ""}
|
||||
</span>
|
||||
))
|
||||
: "условия использования платформы"}
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<p className="text-xs py-2 px-3" style={{ border: "2px solid oklch(0.577 0.245 27.325)", color: "oklch(0.577 0.245 27.325)" }}>
|
||||
{error}
|
||||
|
||||
Reference in New Issue
Block a user