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:
@@ -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 },
|
||||
];
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user