Merge pull request #2 from WIT-LAB-LLC/codex/write-documentation-for-screen-templates-and-builder-1cp4ov
docs: describe funnel templates and builder
This commit is contained in:
commit
65d00bcc3d
232
docs/templates-and-builder.md
Normal file
232
docs/templates-and-builder.md
Normal file
@ -0,0 +1,232 @@
|
||||
# Шаблоны экранов и конструктор воронки
|
||||
|
||||
Этот документ описывает, из каких частей состоит JSON-конфигурация воронки, какие шаблоны экранов доступны в рантайме и как с ними работает конструктор (builder). Используйте его как справочник при ручном редактировании JSON или при настройке воронки через интерфейс администратора.
|
||||
|
||||
## Архитектура воронки
|
||||
|
||||
Воронка описывается объектом `FunnelDefinition` и состоит из двух частей: метаданных и списка экранов. Навигация осуществляется по идентификаторам экранов, а состояние (выборы пользователя) хранится отдельно в рантайме.
|
||||
|
||||
```ts
|
||||
interface FunnelDefinition {
|
||||
meta: {
|
||||
id: string;
|
||||
version?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
firstScreenId?: string; // стартовый экран, по умолчанию первый в списке
|
||||
};
|
||||
defaultTexts?: {
|
||||
nextButton?: string;
|
||||
continueButton?: string;
|
||||
};
|
||||
screens: ScreenDefinition[]; // набор экранов разных шаблонов
|
||||
}
|
||||
```
|
||||
|
||||
Каждый экран обязан иметь уникальный `id` и поле `template`, которое выбирает шаблон визуализации. Дополнительно поддерживаются:
|
||||
|
||||
- `header` — управляет прогресс-баром, заголовком и кнопкой «Назад». По умолчанию шапка показывается, а прогресс вычисляется автоматически в рантайме.
|
||||
- `bottomActionButton` — универсальное описание основной кнопки («Продолжить», «Далее» и т. п.). Шаблон может переопределить или скрыть её.
|
||||
- `navigation` — правила переходов между экранами.
|
||||
|
||||
### Навигация
|
||||
|
||||
Навигация описывается объектом `NavigationDefinition`:
|
||||
|
||||
```ts
|
||||
interface NavigationDefinition {
|
||||
defaultNextScreenId?: string; // переход по умолчанию
|
||||
rules?: Array<{
|
||||
nextScreenId: string; // куда перейти, если условие выполнено
|
||||
conditions: Array<{
|
||||
screenId: string; // экран, чьи ответы проверяем
|
||||
operator?: "includesAny" | "includesAll" | "includesExactly";
|
||||
optionIds: string[]; // выбранные опции, которые проверяются
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
Рантайм использует первый сработавший `rule` и только после этого обращается к `defaultNextScreenId`. Для списков с одиночным выбором и скрытой кнопкой переход совершается автоматически при изменении ответа. Для всех прочих шаблонов пользователь должен нажать действие, сконфигурированное для текущего экрана.
|
||||
|
||||
## Шаблоны экранов
|
||||
|
||||
Ниже приведено краткое описание каждого шаблона и JSON-поле, которое его конфигурирует.
|
||||
|
||||
### Информационный экран (`template: "info"`)
|
||||
|
||||
Используется для показа статических сообщений, промо-блоков или инструкций. Обязательные поля — `id`, `template`, `title`. Дополнительно поддерживаются:
|
||||
|
||||
- `description` — расширенный текст под заголовком.
|
||||
- `icon` — эмодзи или картинка. `type` принимает значения `emoji` или `image`, `value` — символ или URL, `size` — `sm | md | lg | xl`.
|
||||
- `bottomActionButton` — описание кнопки внизу, если нужно отличное от дефолтного текста.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "welcome",
|
||||
"template": "info",
|
||||
"title": { "text": "Добро пожаловать" },
|
||||
"description": { "text": "Заполните короткую анкету, чтобы получить персональное предложение." },
|
||||
"icon": { "type": "emoji", "value": "👋", "size": "lg" },
|
||||
"navigation": { "defaultNextScreenId": "question-1" }
|
||||
}
|
||||
```
|
||||
|
||||
Рантайм выводит заголовок по центру, кнопку «Next» (или `defaultTexts.nextButton`) и позволяет вернуться назад, если это разрешено в `header`. Логика описана в `InfoTemplate` и `buildLayoutQuestionProps` — дополнительные параметры (`font`, `color`, `align`) влияют на типографику.【F:src/components/funnel/templates/InfoTemplate.tsx†L1-L99】【F:src/lib/funnel/types.ts†L74-L131】
|
||||
|
||||
### Экран с вопросом и вариантами (`template: "list"`)
|
||||
|
||||
Базовый интерактивный экран. Поле `list` описывает варианты ответов:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "question-1",
|
||||
"template": "list",
|
||||
"title": { "text": "Какой формат подходит?" },
|
||||
"subtitle": { "text": "Можно выбрать несколько", "color": "muted" },
|
||||
"list": {
|
||||
"selectionType": "multi", // или "single"
|
||||
"options": [
|
||||
{ "id": "opt-online", "label": "Онлайн" },
|
||||
{ "id": "opt-offline", "label": "Офлайн", "description": "в вашем городе" }
|
||||
],
|
||||
"bottomActionButton": { "text": "Сохранить выбор" }
|
||||
},
|
||||
"bottomActionButton": { "show": false },
|
||||
"navigation": {
|
||||
"defaultNextScreenId": "calendar",
|
||||
"rules": [
|
||||
{
|
||||
"nextScreenId": "coupon",
|
||||
"conditions": [{
|
||||
"screenId": "question-1",
|
||||
"operator": "includesAll",
|
||||
"optionIds": ["opt-online", "opt-offline"]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Особенности:
|
||||
|
||||
- `selectionType` определяет поведение: `single` строит радиокнопки, `multi` — чекбоксы. Компоненты `RadioAnswersList` и `SelectAnswersList` получают подготовленные данные из `mapListOptionsToButtons`.
|
||||
- Кнопка действия может описываться либо на уровне `list.bottomActionButton`, либо через общий `bottomActionButton`. В рантайме она скрывается, если `show: false`. Для списков с одиночным выбором и скрытой кнопкой включается автопереход на следующий экран при изменении ответа.【F:src/components/funnel/templates/ListTemplate.tsx†L1-L109】【F:src/components/funnel/FunnelRuntime.tsx†L73-L199】
|
||||
- Ответы сохраняются в массиве строк (идентификаторы опций) и используются навигацией и аналитикой.
|
||||
|
||||
### Экран выбора даты (`template: "date"`)
|
||||
|
||||
Предлагает три выпадающих списка (месяц, день, год) и опциональный блок с отформатированной датой.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "calendar",
|
||||
"template": "date",
|
||||
"title": { "text": "Когда планируете начать?" },
|
||||
"subtitle": { "text": "Выберите ориентировочную дату", "color": "muted" },
|
||||
"dateInput": {
|
||||
"monthLabel": "Месяц",
|
||||
"dayLabel": "День",
|
||||
"yearLabel": "Год",
|
||||
"showSelectedDate": true,
|
||||
"selectedDateLabel": "Вы выбрали"
|
||||
},
|
||||
"infoMessage": { "text": "Мы не будем делиться датой с третьими лицами." },
|
||||
"navigation": { "defaultNextScreenId": "contact" }
|
||||
}
|
||||
```
|
||||
|
||||
Особенности:
|
||||
|
||||
- Значение сохраняется как массив `[month, day, year]` внутри `answers` рантайма.
|
||||
- Кнопка «Next» активируется только после заполнения всех полей. Настройка текстов и подсказок — через объект `dateInput` (placeholder, label, формат для превью).
|
||||
- При `showSelectedDate: true` под кнопкой появляется подтверждающий блок с читабельной датой.【F:src/components/funnel/templates/DateTemplate.tsx†L1-L209】【F:src/lib/funnel/types.ts†L133-L189】
|
||||
|
||||
### Экран формы (`template: "form"`)
|
||||
|
||||
Подходит для сбора контактных данных. Поле `fields` содержит список текстовых инпутов со своими правилами.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "contact",
|
||||
"template": "form",
|
||||
"title": { "text": "Оставьте контакты" },
|
||||
"fields": [
|
||||
{ "id": "name", "label": "Имя", "required": true, "maxLength": 60 },
|
||||
{
|
||||
"id": "email",
|
||||
"label": "E-mail",
|
||||
"type": "email",
|
||||
"validation": {
|
||||
"pattern": "^\\S+@\\S+\\.\\S+$",
|
||||
"message": "Введите корректный e-mail"
|
||||
}
|
||||
}
|
||||
],
|
||||
"validationMessages": {
|
||||
"required": "Поле ${field} обязательно",
|
||||
"invalidFormat": "Неверный формат"
|
||||
},
|
||||
"navigation": { "defaultNextScreenId": "coupon" }
|
||||
}
|
||||
```
|
||||
|
||||
Особенности рантайма:
|
||||
|
||||
- Локальное состояние синхронизируется с глобальным через `onFormDataChange` — данные сериализуются в JSON-строку и хранятся в массиве ответов (первый элемент).【F:src/components/funnel/FunnelRuntime.tsx†L46-L118】
|
||||
- Кнопка продолжения (`defaultTexts.continueButton` или «Continue») активна, если все обязательные поля заполнены. Валидаторы проверяют `required`, `maxLength` и регулярное выражение из `validation.pattern` с кастомными сообщениями.【F:src/components/funnel/templates/FormTemplate.tsx†L1-L119】【F:src/lib/funnel/types.ts†L191-L238】
|
||||
|
||||
### Экран промокода (`template: "coupon"`)
|
||||
|
||||
Отображает купон с акцией и позволяет скопировать промокод.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "coupon",
|
||||
"template": "coupon",
|
||||
"title": { "text": "Поздравляем!" },
|
||||
"subtitle": { "text": "Получите скидку" },
|
||||
"coupon": {
|
||||
"title": { "text": "Скидка 20%" },
|
||||
"offer": {
|
||||
"title": { "text": "-20% на первый заказ" },
|
||||
"description": { "text": "Действует до конца месяца" }
|
||||
},
|
||||
"promoCode": { "text": "START20" },
|
||||
"footer": { "text": "Скопируйте код и введите при оформлении" }
|
||||
},
|
||||
"copiedMessage": "Код {code} скопирован!",
|
||||
"navigation": { "defaultNextScreenId": "final-info" }
|
||||
}
|
||||
```
|
||||
|
||||
`CouponTemplate` копирует код в буфер обмена и показывает уведомление `copiedMessage` (строка с подстановкой `{code}`). Кнопка продолжения использует `defaultTexts.continueButton` или значение «Continue».【F:src/components/funnel/templates/CouponTemplate.tsx†L1-L111】【F:src/lib/funnel/types.ts†L191-L230】
|
||||
|
||||
## Конструктор (Builder)
|
||||
|
||||
Конструктор помогает собирать JSON-конфигурацию и состоит из трёх основных областей:
|
||||
|
||||
1. **Верхняя панель** (`BuilderTopBar`). Позволяет создать пустой проект, загрузить готовый JSON и экспортировать текущую конфигурацию. Импорт использует `deserializeFunnelDefinition`, добавляющий служебные координаты для канваса. Экспорт сериализует состояние обратно в формат `FunnelDefinition` (`serializeBuilderState`).【F:src/components/admin/builder/BuilderTopBar.tsx†L1-L79】【F:src/lib/admin/builder/utils.ts†L1-L58】
|
||||
2. **Канвас** (`BuilderCanvas`). Отображает экраны цепочкой, даёт возможность добавлять новые (`add-screen`), менять порядок drag-and-drop (`reorder-screens`) и выбирать экран для редактирования. Каждый экран показывает тип шаблона, количество опций и ссылку на следующий экран по умолчанию.【F:src/components/admin/builder/BuilderCanvas.tsx†L1-L132】
|
||||
3. **Боковая панель** (`BuilderSidebar`). Содержит две вкладки состояния:
|
||||
- Когда экран не выбран, показываются настройки воронки (ID, заголовок, описание, стартовый экран) и сводка валидации (`validateBuilderState`).【F:src/components/admin/builder/BuilderSidebar.tsx†L1-L188】【F:src/lib/admin/builder/validation.ts†L1-L168】
|
||||
- Для выбранного экрана доступны поля заголовков, параметры списка (тип выбора, опции), правила навигации, кастомизация кнопок и инструмент удаления. Все изменения отправляются через `update-screen`, `update-navigation` и вспомогательные обработчики, формируя корректный JSON.
|
||||
|
||||
### Предпросмотр
|
||||
|
||||
Компонент `BuilderPreview` визуализирует выбранный экран, используя те же шаблоны, что и боевой рантайм (`ListTemplate`, `InfoTemplate` и др.). Для симуляции действий используются заглушки — выбор опций, заполнение формы и навигация обновляют локальное состояние предпросмотра, но не меняют структуру воронки. При переключении экрана состояние сбрасывается, что позволяет увидеть дефолтное поведение каждого шаблона.【F:src/components/admin/builder/BuilderPreview.tsx†L1-L123】
|
||||
|
||||
### Валидация и сериализация
|
||||
|
||||
`validateBuilderState` проверяет уникальность идентификаторов экранов и опций, корректность ссылок в навигации и наличие переходов. Ошибки и предупреждения отображаются в боковой панели. При экспорте координаты канваса удаляются, чтобы JSON соответствовал ожиданиям рантайма. Ответы пользователей рантайм хранит в структуре `Record<string, string[]>`, где ключ — `id` экрана, а значение — массив выбранных значений (опций, компонентов даты или сериализованные данные формы).【F:src/lib/admin/builder/validation.ts†L1-L168】【F:src/lib/admin/builder/utils.ts†L1-L86】【F:src/components/funnel/FunnelRuntime.tsx†L1-L215】
|
||||
|
||||
## Рабочий процесс
|
||||
|
||||
1. Создайте экраны через верхнюю панель или кнопку на канвасе. Каждый новый экран получает уникальный ID (`screen-{n}`).
|
||||
2. Настройте порядок переходов drag-and-drop и установите `firstScreenId`, если стартовать нужно не с первого элемента.
|
||||
3. Заполните контент для каждого шаблона, настройте условия в `navigation.rules` и убедитесь, что `defaultNextScreenId` указан для веток без правил.
|
||||
4. Проверьте сводку валидации — при ошибках экспорт JSON будет возможен, но рантайм может не смочь построить маршрут.
|
||||
5. Экспортируйте JSON и передайте его рантайму (`<FunnelRuntime funnel={definition} initialScreenId={definition.meta.firstScreenId} />`).
|
||||
|
||||
Такой подход гарантирует, что конструктор и рантайм используют одну и ту же схему данных, а визуальные шаблоны ведут себя предсказуемо при изменении конфигурации.
|
||||
Loading…
Reference in New Issue
Block a user