Как устроен FeedbackForm для Quartz 4

FeedbackForm нужен там, где статический Quartz-сайт должен не только показывать страницы, но и принимать нормальную обратную связь: заявку, бриф, контактные данные и короткое описание задачи. Для автора сайта это способ не терять заинтересованного читателя после CTA, а для читателя — понятный маршрут от «хочу обсудить проект» к конкретной форме.

В этом проекте компонент не рисует готовую форму прямо в TSX. Он работает как ресурсный placeholder: подключает стили и inline-скрипт, а реальный DOM формы создаётся уже в браузере по JSON-конфигу.

Для Quartz со SPA-навигацией такой подход особенно важен. Страница может меняться без полной перезагрузки, поэтому форма должна аккуратно монтироваться повторно после события nav, не ломая переходы и не превращая Markdown-страницу в набор ручных HTML-вставок.

Компонент как placeholder

quartz/components/FeedbackForm.tsx устроен минимально:

const FeedbackForm: QuartzComponent = () => {
  return <div class="feedback-form-resources" aria-hidden="true"></div>
}
 
FeedbackForm.css = style
FeedbackForm.afterDOMLoaded = script

На странице этот div сам по себе ничего не показывает, и это нормально: он не является местом формы. Его задача тише и важнее — подключить CSS и feedbackForm.inline.ts к ресурсам Quartz, чтобы настоящая форма могла появиться там, где для неё оставлен отдельный placeholder.

Где создаётся настоящая форма

Настоящая форма создаётся в quartz/components/scripts/feedbackForm.inline.ts. Скрипт ищет элементы вида:

<div class="feedback-form" data-source="/static/data/feedback-form.json"></div>

Дальше начинается пользовательская часть сценария: читатель видит карточку формы, заполняет поля, получает понятный статус отправки, а автор сайта получает структурированный payload вместо разрозненного сообщения. Технически после поиска placeholder скрипт:

  • загружает JSON из data-source;
  • создаёт поля;
  • валидирует обязательные значения;
  • собирает JSON-payload;
  • отправляет его на action;
  • показывает статус отправки прямо в карточке.

Скрипт подписывается на SPA-событие nav, чтобы форма продолжала работать после внутренних переходов Quartz.

Конфиг формы

Данные лежат в quartz/static/data/feedback-form.json. Это место описывает не внешний вид ради внешнего вида, а смысл разговора с посетителем. Сейчас это не абстрактная форма «напишите нам», а бриф на разработку:

  • имя;
  • компания;
  • телефон;
  • email;
  • Telegram;
  • тип услуги;
  • scope работ;
  • бюджет через range;
  • сроки;
  • материалы;
  • описание задачи;
  • согласие на обработку персональных данных.

action указывает на endpoint https://app.slavx.ru/api/v1/f/12a7dd50d5c0, метод — POST.

Почему JSON, а не Markdown

Форма быстро становится неудобной, если описывать её в Markdown или HTML-вставках: поля перемешиваются с текстом страницы, сложнее поддерживать обязательность, варианты выбора и служебные параметры отправки. JSON даёт нормальную структуру:

{
  "type": "email",
  "name": "email",
  "label": "Email",
  "placeholder": "you@example.com",
  "required": true,
  "autocomplete": "email"
}

Так проще менять поля, порядок, варианты select/checkbox и обязательность. Код компонента при этом остаётся тем же, а статья или страница не превращается в технический черновик с большим куском формы внутри Markdown.

Подключение в layout

FeedbackForm подключён глобально в sharedPageComponents.afterBody:

afterBody: [
  Component.SiteGeometryBackground(siteGeometryBackgroundData),
  Component.ServicesCarousel(),
  Component.FeedbackForm(),
  Component.HomeCallback(),
  Component.CookieConsent(),
]

Это не значит, что форма видна на каждой странице. Глобально подключаются только ресурсы, чтобы они были готовы к работе после загрузки и внутренних переходов. Конкретное место формы определяется HTML-placeholder в контенте или компоненте.

Связь с HomeCallback

Рядом подключён HomeCallback. Он тоже выглядит как ресурсный компонент: отдаёт скрытый placeholder и подключает homeCallback.inline.ts. По смыслу это соседняя часть того же пользовательского сценария: CTA на главной или в лендинговых блоках подталкивает читателя к действию, callback/brief-механика помогает начать контакт без лишних шагов, а FeedbackForm отвечает за полноценную форму.

Поэтому при изменении формы нужно проверять не только FeedbackForm, но и кнопки, которые открывают или ведут к ней на главной и странице контактов.

Юридические документы

В форме есть обязательное согласие на обработку персональных данных. Это не декоративный checkbox: он связан с юридической частью сайта. На лендинговых блоках также есть тексты и ссылки на legal-документы, например в contact CTA главной.

Если меняется текст согласия, privacy note или сценарий отправки, нужно сверять это с юридическими страницами сайта. Форма собирает персональные данные, значит текст должен быть не только красивым, но и аккуратным.

Что менять на практике

Практический плюс такой схемы в том, что большинство изменений остаётся на уровне данных. Если нужно уточнить вопрос, поменять подпись или переставить поля, не обязательно переписывать компонент. Чаще всего правятся только:

  • title и subtitle формы;
  • набор fields;
  • варианты options;
  • privacyNote;
  • submitLabel;
  • endpoint action, если меняется backend.

Код компонента трогать стоит только если нужен новый тип поля или другая логика отправки. Так меньше риск случайно сломать поведение формы, SPA-переходы или соседний сценарий с HomeCallback.

Итог

FeedbackForm в текущем Quartz-проекте — это не «форма в TSX», а связка из placeholder-компонента, inline-скрипта и JSON-конфига. Идея этой связки простая: дать статическому сайту живой канал обратной связи, но не смешивать контент, ресурсы и клиентскую логику в одном месте. Markdown остаётся чистым, форма настраивается данными, а клиентское поведение переживает SPA-переходы.