feat: centralize popup texts in new admin tab /admin/popups

- New admin page for shared popup texts (success, waiting list, error, Instagram hint)
- Removed popup fields from MC and Open Day admin editors
- All SignupModals now read from centralized popups config
- Stored as "popups" section in DB with fallback defaults
This commit is contained in:
2026-03-25 23:48:06 +03:00
parent 983bf296fc
commit 6c485872b0
11 changed files with 100 additions and 62 deletions
+2
View File
@@ -23,6 +23,7 @@ import {
ChevronLeft,
ClipboardList,
DoorOpen,
MessageSquare,
} from "lucide-react";
const NAV_ITEMS = [
@@ -39,6 +40,7 @@ const NAV_ITEMS = [
{ href: "/admin/pricing", label: "Цены", icon: DollarSign },
{ href: "/admin/faq", label: "FAQ", icon: HelpCircle },
{ href: "/admin/news", label: "Новости", icon: Newspaper },
{ href: "/admin/popups", label: "Всплывающие окна", icon: MessageSquare },
{ href: "/admin/contact", label: "Контакты", icon: Phone },
];
-17
View File
@@ -34,8 +34,6 @@ function PriceField({ label, value, onChange, placeholder }: { label: string; va
interface MasterClassesData {
title: string;
successMessage?: string;
waitingListText?: string;
items: MasterClassItem[];
}
@@ -386,21 +384,6 @@ export default function MasterClassesEditorPage() {
onChange={(v) => update({ ...data, title: v })}
/>
<InputField
label="Текст после записи (success popup)"
value={data.successMessage || ""}
onChange={(v) => update({ ...data, successMessage: v || undefined })}
placeholder="Вы записаны! Мы свяжемся с вами"
/>
<TextareaField
label="Текст для листа ожидания"
value={data.waitingListText || ""}
onChange={(v) => update({ ...data, waitingListText: v || undefined })}
placeholder="Все места заняты, но мы добавили вас в лист ожидания..."
rows={2}
/>
<ArrayEditor
label="Мастер-классы"
items={data.items}
-25
View File
@@ -19,8 +19,6 @@ interface OpenDayEvent {
discountThreshold: number;
minBookings: number;
maxParticipants: number;
successMessage?: string;
waitingListText?: string;
active: boolean;
}
@@ -102,29 +100,6 @@ function EventSettings({
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label className="block text-sm text-neutral-400 mb-1.5">Текст после записи</label>
<textarea
value={event.successMessage || ""}
onChange={(e) => onChange({ successMessage: e.target.value || undefined })}
rows={2}
className="w-full rounded-lg border border-white/10 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 outline-none focus:border-gold transition-colors resize-none"
placeholder="Вы записаны!"
/>
</div>
<div>
<label className="block text-sm text-neutral-400 mb-1.5">Текст для листа ожидания</label>
<textarea
value={event.waitingListText || ""}
onChange={(e) => onChange({ waitingListText: e.target.value || undefined })}
rows={2}
className="w-full rounded-lg border border-white/10 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 outline-none focus:border-gold transition-colors resize-none"
placeholder="Все места заняты, но мы добавили вас в лист ожидания..."
/>
</div>
</div>
<div>
<label className="block text-sm text-neutral-400 mb-1.5">Цена за занятие (BYN)</label>
<input
+56
View File
@@ -0,0 +1,56 @@
"use client";
import { SectionEditor } from "../_components/SectionEditor";
import { TextareaField } from "../_components/FormField";
interface PopupsData {
successMessage: string;
waitingListText: string;
errorMessage: string;
instagramHint: string;
}
export default function PopupsEditorPage() {
return (
<SectionEditor<PopupsData>
sectionKey="popups"
title="Тексты всплывающих окон"
>
{(data, update) => (
<div className="space-y-6">
<p className="text-sm text-neutral-500">
Эти тексты используются во всех формах записи: мастер-классы, день открытых дверей, групповые занятия.
</p>
<TextareaField
label="Успешная запись"
value={data.successMessage}
onChange={(v) => update({ ...data, successMessage: v })}
rows={2}
/>
<TextareaField
label="Лист ожидания"
value={data.waitingListText}
onChange={(v) => update({ ...data, waitingListText: v })}
rows={3}
/>
<TextareaField
label="Ошибка при записи"
value={data.errorMessage}
onChange={(v) => update({ ...data, errorMessage: v })}
rows={2}
/>
<TextareaField
label="Ссылка на Instagram (текст под сообщениями)"
value={data.instagramHint}
onChange={(v) => update({ ...data, instagramHint: v })}
rows={1}
/>
</div>
)}
</SectionEditor>
);
}
+2 -2
View File
@@ -30,7 +30,7 @@ export default function HomePage() {
<Header />
<main>
<Hero data={content.hero} />
{openDayData && <OpenDay data={openDayData} />}
{openDayData && <OpenDay data={openDayData} popups={content.popups} />}
<About
data={content.about}
stats={{
@@ -41,7 +41,7 @@ export default function HomePage() {
/>
<Team data={content.team} schedule={content.schedule.locations} />
<Classes data={content.classes} />
<MasterClasses data={content.masterClasses} regCounts={mcRegCounts} />
<MasterClasses data={content.masterClasses} regCounts={mcRegCounts} popups={content.popups} />
<Schedule data={content.schedule} classItems={content.classes.items} />
<Pricing data={content.pricing} />
<News data={content.news} />