fix
This commit is contained in:
parent
b57e99e472
commit
8a978cbf06
292
AGE_SYSTEM.md
292
AGE_SYSTEM.md
@ -1,292 +0,0 @@
|
||||
# 🎂 Система работы с возрастом WitLab Funnel
|
||||
|
||||
## Описание
|
||||
|
||||
Универсальная система автоматического расчета возраста и определения поколений из даты рождения пользователя. Интегрируется с системой вариативности для создания возрастных условий навигации.
|
||||
|
||||
## Возможности
|
||||
|
||||
### 🎯 **Автоматический расчет из даты:**
|
||||
- **Точный возраст** (например: 25, 30, 45 лет)
|
||||
- **Возрастные группы** (18-21, 22-25, 26-30, 31-35, 36-40, 41-45, 46-50, 51-60, 60+)
|
||||
- **Поколения** (Generation Z, Millennials, Generation X, Boomers, Silent Generation)
|
||||
- **Комбинации** со знаками зодиака
|
||||
|
||||
### 🎨 **Красивый UI для админки:**
|
||||
- Селектор возрастных групп с описаниями
|
||||
- Селектор поколений с иконками
|
||||
- Возможность добавлять кастомные диапазоны
|
||||
- Живое превью выбранных условий
|
||||
|
||||
## Технические детали
|
||||
|
||||
### 📊 **Возрастные группы:**
|
||||
```typescript
|
||||
export const AGE_GROUPS = [
|
||||
{ id: "18-21", name: "18-21 год", min: 18, max: 21, description: "Студенческий возраст" },
|
||||
{ id: "22-25", name: "22-25 лет", min: 22, max: 25, description: "Молодые профессионалы" },
|
||||
{ id: "26-30", name: "26-30 лет", min: 26, max: 30, description: "Карьерный рост" },
|
||||
{ id: "31-35", name: "31-35 лет", min: 31, max: 35, description: "Становление личности" },
|
||||
{ id: "36-40", name: "36-40 лет", min: 36, max: 40, description: "Зрелость и стабильность" },
|
||||
{ id: "41-45", name: "41-45 лет", min: 41, max: 45, description: "Средний возраст" },
|
||||
{ id: "46-50", name: "46-50 лет", min: 46, max: 50, description: "Жизненный опыт" },
|
||||
{ id: "51-60", name: "51-60 лет", min: 51, max: 60, description: "Зрелые отношения" },
|
||||
{ id: "60+", name: "60+ лет", min: 60, max: 120, description: "Золотой возраст" },
|
||||
] as const;
|
||||
```
|
||||
|
||||
### 🚀 **Поколения:**
|
||||
```typescript
|
||||
export const GENERATION_GROUPS = [
|
||||
{ id: "gen-z", name: "Поколение Z", minYear: 1997, maxYear: 2012, description: "Цифровые аборигены" },
|
||||
{ id: "millennials", name: "Миллениалы", minYear: 1981, maxYear: 1996, description: "Поколение интернета" },
|
||||
{ id: "gen-x", name: "Поколение X", minYear: 1965, maxYear: 1980, description: "Поколение перемен" },
|
||||
{ id: "boomers", name: "Бумеры", minYear: 1946, maxYear: 1964, description: "Послевоенное поколение" },
|
||||
{ id: "silent", name: "Молчаливое поколение", minYear: 1928, maxYear: 1945, description: "Довоенное поколение" },
|
||||
] as const;
|
||||
```
|
||||
|
||||
### ⚡ **Основные функции:**
|
||||
|
||||
#### 1. **Расчет возраста:**
|
||||
```typescript
|
||||
// Из даты рождения
|
||||
calculateAge(new Date(1987, 3, 8)); // → 36
|
||||
|
||||
// Из массива [month, day, year]
|
||||
calculateAgeFromArray([4, 8, 1987]); // → 36
|
||||
```
|
||||
|
||||
#### 2. **Определение группы:**
|
||||
```typescript
|
||||
getAgeGroup(25); // → { id: "22-25", name: "22-25 лет", ... }
|
||||
getGeneration(1987); // → { id: "millennials", name: "Миллениалы", ... }
|
||||
```
|
||||
|
||||
#### 3. **Создание значений для навигации:**
|
||||
```typescript
|
||||
createAgeValue(25); // → "22-25"
|
||||
createGenerationValue(1987); // → "millennials"
|
||||
```
|
||||
|
||||
## Использование в вариативности
|
||||
|
||||
### 📅 **Date экраны автоматически:**
|
||||
Когда пользователь вводит дату рождения в date экране, система автоматически добавляет к ответам:
|
||||
|
||||
**Пример:** Дата рождения 8 апреля 1987 года `[4, 8, 1987]`
|
||||
|
||||
**Автоматически добавляется в ответы:**
|
||||
```typescript
|
||||
[
|
||||
"4", "8", "1987", // Исходная дата
|
||||
"36-40", // Возрастная группа
|
||||
"age-36", // Точный возраст
|
||||
"millennials", // Поколение
|
||||
"aries" // Знак зодиака (из существующей системы)
|
||||
]
|
||||
```
|
||||
|
||||
### 🎯 **Условия навигации:**
|
||||
|
||||
#### **По возрастным группам:**
|
||||
```json
|
||||
{
|
||||
"conditions": [{
|
||||
"screenId": "birth-date",
|
||||
"conditionType": "values",
|
||||
"operator": "includesAny",
|
||||
"values": ["22-25", "26-30"]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
#### **По поколениям:**
|
||||
```json
|
||||
{
|
||||
"conditions": [{
|
||||
"screenId": "birth-date",
|
||||
"conditionType": "values",
|
||||
"operator": "equals",
|
||||
"values": ["millennials"]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
#### **Комбинированные условия:**
|
||||
```json
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"screenId": "birth-date",
|
||||
"conditionType": "values",
|
||||
"operator": "includesAny",
|
||||
"values": ["gen-z", "millennials"]
|
||||
},
|
||||
{
|
||||
"screenId": "birth-date",
|
||||
"conditionType": "values",
|
||||
"operator": "includesAny",
|
||||
"values": ["aries", "leo", "sagittarius"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## UI компоненты
|
||||
|
||||
### 🎨 **AgeSelector для админки:**
|
||||
```tsx
|
||||
<AgeSelector
|
||||
selectedValues={["22-25", "millennials"]}
|
||||
onToggleValue={(value) => console.log('Selected:', value)}
|
||||
onAddCustomValue={(value) => console.log('Added:', value)}
|
||||
/>
|
||||
```
|
||||
|
||||
**Возможности:**
|
||||
- 🎂 Сетка возрастных групп с описаниями
|
||||
- 🚀 Список поколений с иконками
|
||||
- 🎯 Поле для кастомных диапазонов (25-35, 40+)
|
||||
- 📋 Превью выбранных значений с цветовым кодированием
|
||||
|
||||
### 📊 **Интеграция в ScreenVariantsConfig:**
|
||||
Для date экранов автоматически показываются:
|
||||
1. **AgeSelector** - для возрастных условий
|
||||
2. **ZodiacSelector** - для знаков зодиака
|
||||
3. **Умная фильтрация** - каждый селектор показывает только свои значения
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### 💖 **Романтические предпочтения по возрасту:**
|
||||
```json
|
||||
{
|
||||
"id": "young-romance",
|
||||
"variants": [{
|
||||
"conditions": [{
|
||||
"screenId": "birth-date",
|
||||
"conditionType": "values",
|
||||
"operator": "includesAny",
|
||||
"values": ["18-21", "22-25"]
|
||||
}],
|
||||
"overrides": {
|
||||
"title": { "text": "Найти **молодую любовь**!" },
|
||||
"description": { "text": "Специально для **молодых сердец** - найдем твою половинку среди сверстников!" }
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### 🚀 **Карьерные амбиции по поколениям:**
|
||||
```json
|
||||
{
|
||||
"id": "career-focus",
|
||||
"variants": [{
|
||||
"conditions": [{
|
||||
"screenId": "birth-date",
|
||||
"conditionType": "values",
|
||||
"operator": "equals",
|
||||
"values": ["millennials"]
|
||||
}],
|
||||
"overrides": {
|
||||
"title": { "text": "Карьера + **отношения**" },
|
||||
"description": { "text": "Для **миллениалов** - найдем партнера, который поддержит твои амбиции!" }
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### 🎯 **Зрелые отношения:**
|
||||
```json
|
||||
{
|
||||
"id": "mature-love",
|
||||
"variants": [{
|
||||
"conditions": [{
|
||||
"screenId": "birth-date",
|
||||
"conditionType": "values",
|
||||
"operator": "includesAny",
|
||||
"values": ["46-50", "51-60", "60+"]
|
||||
}],
|
||||
"overrides": {
|
||||
"title": { "text": "**Зрелая любовь**" },
|
||||
"description": { "text": "Для тех, кто знает цену **настоящим чувствам** и **жизненному опыту**" }
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## Архитектура
|
||||
|
||||
### 📁 **Файловая структура:**
|
||||
```
|
||||
src/lib/age-utils.ts # Основные утилиты возраста
|
||||
src/components/admin/builder/
|
||||
├── AgeSelector.tsx # UI селектор для админки
|
||||
├── AgeDemo.tsx # Демо компонент
|
||||
└── ScreenVariantsConfig.tsx # Интеграция в вариативность
|
||||
src/lib/funnel/navigation.ts # Интеграция в навигацию
|
||||
```
|
||||
|
||||
### 🔧 **Интеграция в систему:**
|
||||
|
||||
#### **1. Автоматический расчет (navigation.ts):**
|
||||
```typescript
|
||||
// При получении ответов из date экрана автоматически добавляем:
|
||||
const age = calculateAgeFromArray(dateArray);
|
||||
const ageGroup = createAgeValue(age);
|
||||
const generation = createGenerationValue(year);
|
||||
const zodiac = getZodiacSign(month, day);
|
||||
|
||||
enhancedAnswers.push(ageGroup, `age-${age}`, generation, zodiac);
|
||||
```
|
||||
|
||||
#### **2. UI селекция (AgeSelector.tsx):**
|
||||
```typescript
|
||||
// Красивые карточки для выбора условий
|
||||
{AGE_GROUPS.map((group) => (
|
||||
<button onClick={() => onToggleValue(group.id)}>
|
||||
🎂 {group.name} - {group.description}
|
||||
</button>
|
||||
))}
|
||||
```
|
||||
|
||||
#### **3. Валидация диапазонов:**
|
||||
```typescript
|
||||
// Проверка попадания возраста в диапазон
|
||||
isAgeInRange(25, "22-25"); // → true
|
||||
isAgeInRange(30, "18-21"); // → false
|
||||
```
|
||||
|
||||
## Преимущества
|
||||
|
||||
### ✅ **Автоматизация:**
|
||||
- Пользователь вводит только дату рождения
|
||||
- Система автоматически рассчитывает все значения
|
||||
- Не нужно спрашивать возраст отдельно
|
||||
|
||||
### ✅ **Гибкость:**
|
||||
- Поддержка любых возрастных диапазонов
|
||||
- Кастомные значения (25-35, 40+)
|
||||
- Комбинации с другими условиями
|
||||
|
||||
### ✅ **UX дружелюбность:**
|
||||
- Понятные названия групп
|
||||
- Описания для каждой группы
|
||||
- Иконки для поколений
|
||||
- Превью выбранных условий
|
||||
|
||||
### ✅ **Маркетинговая сегментация:**
|
||||
- Готовые возрастные группы для таргетинга
|
||||
- Поколенческая сегментация
|
||||
- Психологические портреты по возрасту
|
||||
|
||||
## Совместимость
|
||||
|
||||
- ✅ **React 18+**
|
||||
- ✅ **TypeScript 5+**
|
||||
- ✅ **Next.js 14+**
|
||||
- ✅ **Существующая система зодиака**
|
||||
- ✅ **Система вариативности**
|
||||
- ✅ **Обратная совместимость** с существующими воронками
|
||||
|
||||
**💡 Теперь можно создавать условия навигации на основе возраста пользователя, автоматически рассчитанного из даты рождения!**
|
||||
101
FIXES-SUMMARY.md
101
FIXES-SUMMARY.md
@ -1,101 +0,0 @@
|
||||
# 🔧 Исправления проблем админки
|
||||
|
||||
## ✅ Исправленные проблемы:
|
||||
|
||||
### 1. **🌐 Воронки не открывались для прохождения**
|
||||
**Проблема:** Сервер пытался читать только файлы JSON, но не из базы данных.
|
||||
|
||||
**Исправление:**
|
||||
- Обновлен `/src/app/[funnelId]/[screenId]/page.tsx`
|
||||
- Добавлена функция `loadFunnelFromDatabase()`
|
||||
- Теперь сначала загружает из MongoDB, потом fallback на JSON файлы
|
||||
- Изменено `dynamic = "force-dynamic"` для поддержки базы данных
|
||||
|
||||
**Результат:** ✅ Воронки из базы данных теперь открываются для прохождения
|
||||
|
||||
### 2. **📏 Унифицированы размеры сайдбара и предпросмотра**
|
||||
**Проблема:** Разные размеры панелей создавали визуальную несогласованность.
|
||||
|
||||
**Исправление в макете билдера (`/src/app/admin/builder/[id]/page.tsx`):**
|
||||
- **Сайдбар:** `w-[360px]` (фиксированный)
|
||||
- **Предпросмотр:** `w-[360px]`
|
||||
- **Оба:** `shrink-0` - не сжимаются
|
||||
|
||||
**Результат:** ✅ Одинаковые размеры боковых панелей — 360px
|
||||
|
||||
### 3. **🎯 Предпросмотр больше не сжимается**
|
||||
**Проблема:** Предпросмотр мог сжиматься и терять пропорции.
|
||||
|
||||
**Исправление:**
|
||||
- Добавлен `shrink-0` для предпросмотра
|
||||
- Фиксированная ширина `w-[360px]`
|
||||
- Canvas остается flex-1 и адаптируется к доступному пространству
|
||||
|
||||
**Результат:** ✅ Предпросмотр сохраняет размеры как заложено изначально
|
||||
|
||||
### 4. **⏪ Реализована рабочая система Undo/Redo**
|
||||
**Проблема:** Старые кнопки были заглушками и не работали.
|
||||
|
||||
**Исправление:**
|
||||
- Добавлен `BuilderUndoRedoProvider` на базе снепшотов состояния (`/src/lib/admin/builder/useSimpleUndoRedo.ts`)
|
||||
- Горячие клавиши Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z и Ctrl/Cmd+Y
|
||||
- Автоматическое сохранение ключевых изменений состояния
|
||||
|
||||
**Результат:** 🔧 Кнопки и горячие клавиши Undo/Redo работают и управляют историей изменений
|
||||
|
||||
## 🚀 Текущий статус:
|
||||
|
||||
### ✅ **Полностью готово:**
|
||||
1. **База данных** - все воронки загружаются из MongoDB
|
||||
2. **Размеры панелей** - унифицированы и зафиксированы (360px)
|
||||
3. **Предпросмотр** - не сжимается, сохраняет пропорции
|
||||
4. **Сборка проекта** - успешно собирается без ошибок
|
||||
5. **Undo/Redo система** - полностью работает с горячими клавишами
|
||||
|
||||
### ✨ **Дополнительные улучшения:**
|
||||
- **Server-side загрузка** из MongoDB вместо HTTP запросов
|
||||
- **Автоматическое сохранение** истории при значимых изменениях
|
||||
- **Keyboard shortcuts** - Ctrl+Z, Ctrl+Y, Ctrl+Shift+Z работают
|
||||
|
||||
## 📋 Следующие шаги для завершения Undo/Redo:
|
||||
|
||||
### 1. **Подключить команды к действиям редактора:**
|
||||
```typescript
|
||||
// Пример интеграции в компонентах редактора
|
||||
const undoRedo = useBuilderUndoRedo();
|
||||
|
||||
const handleUpdateScreen = (screenId: string, property: string, newValue: any) => {
|
||||
const oldValue = getCurrentValue(screenId, property);
|
||||
undoRedo.updateScreenProperty(screenId, property, newValue, oldValue);
|
||||
};
|
||||
```
|
||||
|
||||
### 2. **Добавить команды для:**
|
||||
- Изменение текста экранов
|
||||
- Добавление/удаление вариантов в списках
|
||||
- Изменение навигации между экранами
|
||||
- Добавление/удаление экранов
|
||||
- Изменение настроек воронки
|
||||
|
||||
### 3. **Интеграция с базой данных:**
|
||||
- Сохранение baseline точек при save/publish
|
||||
- Очистка истории при загрузке новой воронки
|
||||
|
||||
## 🎯 Используемые лучшие практики:
|
||||
|
||||
### **Command Pattern over Memento:**
|
||||
- Granular операции вместо снимков состояния
|
||||
- Поддержка side-effects и API calls
|
||||
- Совместимость с collaborative editing
|
||||
|
||||
### **Time-based Linear History:**
|
||||
- Избегание "anxiety" от потери веток истории
|
||||
- Intuitive UX где каждый шаг увеличивает счетчик
|
||||
- Как в Emacs - все изменения сохраняются
|
||||
|
||||
### **Session-scoped с возможностью расширения:**
|
||||
- Привязка к сессии редактирования
|
||||
- Возможность будущего расширения на user-scope
|
||||
- Cleanup при закрытии сессии
|
||||
|
||||
**Архитектура готова для production использования! 🚀**
|
||||
201
IMPORT-GUIDE.md
201
IMPORT-GUIDE.md
@ -1,201 +0,0 @@
|
||||
# 📥 Импорт воронок в базу данных
|
||||
|
||||
Этот скрипт позволяет импортировать все существующие воронки из папки `public/funnels/` в базу данных MongoDB.
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
```bash
|
||||
# 1. Убедитесь что MongoDB запущен и настроен .env.local
|
||||
npm run import:funnels
|
||||
```
|
||||
|
||||
## 📋 Требования
|
||||
|
||||
### 1. MongoDB подключение
|
||||
Убедитесь что в `.env.local` указан правильный `MONGODB_URI`:
|
||||
|
||||
```bash
|
||||
# .env.local
|
||||
MONGODB_URI=mongodb://localhost:27017/witlab-funnel
|
||||
# или для MongoDB Atlas:
|
||||
# MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/witlab-funnel
|
||||
```
|
||||
|
||||
### 2. Структура файлов
|
||||
Скрипт ищет JSON файлы в папке `public/funnels/`. Каждый файл должен содержать валидную структуру воронки:
|
||||
|
||||
```json
|
||||
{
|
||||
"meta": {
|
||||
"id": "unique-funnel-id",
|
||||
"title": "Название воронки",
|
||||
"description": "Описание воронки",
|
||||
"firstScreenId": "screen-1"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "screen-1",
|
||||
"template": "info",
|
||||
"title": { "text": "Заголовок" }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Что делает скрипт
|
||||
|
||||
### ✅ Проверяет и валидирует
|
||||
- Подключение к MongoDB
|
||||
- Структуру JSON файлов
|
||||
- Наличие обязательных полей (`meta.id`, `screens`)
|
||||
|
||||
### 📦 Импортирует данные
|
||||
- Создает записи в коллекции `funnels`
|
||||
- Генерирует метаданные (название, описание)
|
||||
- Устанавливает статус `published`
|
||||
- Добавляет информацию об импорте
|
||||
|
||||
### 🔍 Избегает дубликатов
|
||||
- Проверяет существование воронки по `meta.id`
|
||||
- Пропускает уже импортированные файлы
|
||||
- Показывает детальный отчет
|
||||
|
||||
## 📈 Результат работы
|
||||
|
||||
После запуска вы увидите:
|
||||
|
||||
```
|
||||
🚀 Starting funnel import process...
|
||||
|
||||
✅ Connected to MongoDB
|
||||
📁 Found 12 funnel files in public/funnels/
|
||||
|
||||
📥 Starting import of 12 funnels...
|
||||
|
||||
[1/12] Processing funnel-test.json...
|
||||
✅ Imported as "Funnel Test" (ID: funnel-test)
|
||||
|
||||
[2/12] Processing ru-career-accelerator.json...
|
||||
⏭️ Skipped - already exists (ID: ru-career-accelerator)
|
||||
|
||||
...
|
||||
|
||||
📊 Import Summary:
|
||||
==================
|
||||
✅ Successfully imported: 10
|
||||
⏭️ Already existed: 2
|
||||
⚠️ Skipped (invalid): 0
|
||||
❌ Errors: 0
|
||||
📁 Total processed: 12
|
||||
|
||||
📋 Imported Funnels:
|
||||
• Funnel Test (funnel-test) - funnel-test.json
|
||||
• Career Accelerator (ru-career-accelerator) - ru-career-accelerator.json
|
||||
...
|
||||
|
||||
🎉 Import process completed!
|
||||
```
|
||||
|
||||
## 🎯 Генерация метаданных
|
||||
|
||||
Скрипт автоматически генерирует удобные названия и описания:
|
||||
|
||||
### Название воронки
|
||||
1. **Из `meta.title`** (если есть)
|
||||
2. **Из `meta.id`** преобразованного в читаемый вид
|
||||
- `funnel-test` → `Funnel Test`
|
||||
- `ru-career-accelerator` → `Ru Career Accelerator`
|
||||
3. **По умолчанию**: `Imported Funnel`
|
||||
|
||||
### Описание воронки
|
||||
1. **Из `meta.description`** (если есть)
|
||||
2. **Автогенерация** на основе:
|
||||
- Количества экранов: "Воронка с 5 экранами"
|
||||
- Используемых шаблонов: "Типы: info, form, list"
|
||||
- Источника: "Импортирована из JSON файла"
|
||||
|
||||
## 🗃️ Структура в базе данных
|
||||
|
||||
Каждая импортированная воронка сохраняется как:
|
||||
|
||||
```json
|
||||
{
|
||||
"_id": "ObjectId",
|
||||
"funnelData": { /* Оригинальная структура JSON */ },
|
||||
"name": "Сгенерированное название",
|
||||
"description": "Сгенерированное описание",
|
||||
"status": "published",
|
||||
"version": 1,
|
||||
"createdBy": "import-script",
|
||||
"usage": { "totalViews": 0, "totalCompletions": 0 },
|
||||
"createdAt": "2025-01-27T02:13:24.000Z",
|
||||
"updatedAt": "2025-01-27T02:13:24.000Z",
|
||||
"publishedAt": "2025-01-27T02:13:24.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 Устранение проблем
|
||||
|
||||
### Ошибка подключения к MongoDB
|
||||
```
|
||||
❌ Failed to connect to MongoDB: connect ECONNREFUSED 127.0.0.1:27017
|
||||
```
|
||||
**Решение**: Запустите MongoDB или проверьте `MONGODB_URI`
|
||||
|
||||
### Файлы не найдены
|
||||
```
|
||||
📭 No funnel files found to import.
|
||||
```
|
||||
**Решение**: Убедитесь что JSON файлы находятся в `public/funnels/`
|
||||
|
||||
### Валидационные ошибки
|
||||
```
|
||||
⚠️ Validation warnings for example.json: Missing meta.id
|
||||
```
|
||||
**Решение**: Проверьте структуру JSON файла
|
||||
|
||||
### Дубликаты в базе
|
||||
```
|
||||
⏭️ Skipped - already exists (ID: funnel-test)
|
||||
```
|
||||
**Это нормально**: Скрипт не перезаписывает существующие воронки
|
||||
|
||||
## 📱 После импорта
|
||||
|
||||
### Где найти импортированные воронки
|
||||
1. **Админка**: `http://localhost:3000/admin`
|
||||
2. **Прямой доступ**: `http://localhost:3000/{funnel-id}`
|
||||
3. **Редактирование**: `/admin/builder/{database-id}`
|
||||
|
||||
### Что можно делать
|
||||
- ✅ Редактировать в билдере
|
||||
- ✅ Дублировать и создавать вариации
|
||||
- ✅ Просматривать статистику
|
||||
- ✅ Экспортировать обратно в JSON
|
||||
- ✅ Публиковать/архивировать
|
||||
|
||||
### Совместимость
|
||||
- ✅ Оригинальные JSON файлы продолжают работать
|
||||
- ✅ Импортированные воронки имеют приоритет при загрузке
|
||||
- ✅ Полная обратная совместимость
|
||||
|
||||
## 🔄 Повторный запуск
|
||||
|
||||
Скрипт можно запускать несколько раз:
|
||||
- **Безопасно**: не создает дубликаты
|
||||
- **Умно**: импортирует только новые файлы
|
||||
- **Быстро**: пропускает уже обработанные
|
||||
|
||||
## 📝 Логи и отчеты
|
||||
|
||||
Скрипт выводит подробную информацию:
|
||||
- 📁 Количество найденных файлов
|
||||
- 🔄 Прогресс обработки каждого файла
|
||||
- ✅ Успешные импорты с деталями
|
||||
- ⚠️ Предупреждения и пропуски
|
||||
- ❌ Ошибки с объяснениями
|
||||
- 📊 Итоговая сводка
|
||||
|
||||
---
|
||||
|
||||
**💡 Совет**: Запустите скрипт после настройки базы данных, чтобы быстро мигрировать все существующие воронки в новую админку!
|
||||
180
MARKUP.md
180
MARKUP.md
@ -1,180 +0,0 @@
|
||||
# 🎨 Система разметки текста WitLab Funnel
|
||||
|
||||
## Описание
|
||||
|
||||
Универсальная система разметки позволяет выделять части текста **жирным шрифтом** в любых текстовых полях воронки. Система автоматически обнаруживает разметку и применяет стили.
|
||||
|
||||
## Синтаксис
|
||||
|
||||
### **Жирный текст**
|
||||
```
|
||||
**текст** - выделяет текст жирным шрифтом
|
||||
```
|
||||
|
||||
### Примеры использования:
|
||||
|
||||
#### 📝 **В заголовках:**
|
||||
```
|
||||
"Добро пожаловать в **WitLab**!"
|
||||
```
|
||||
Результат: "Добро пожаловать в **WitLab**!"
|
||||
|
||||
#### 💰 **В предложениях скидок:**
|
||||
```
|
||||
"**50%** скидка только сегодня!"
|
||||
```
|
||||
Результат: "**50%** скидка только сегодня!"
|
||||
|
||||
#### 💖 **В результатах анализа:**
|
||||
```
|
||||
"Ваш **идеальный партнер** найден на основе анализа ваших ответов"
|
||||
```
|
||||
Результат: "Ваш **идеальный партнер** найден на основе анализа ваших ответов"
|
||||
|
||||
#### 👤 **С именами пользователей:**
|
||||
```
|
||||
"Поздравляем, **Анна**! Ваш портрет готов."
|
||||
```
|
||||
Результат: "Поздравляем, **Анна**! Ваш портрет готов."
|
||||
|
||||
## Где работает
|
||||
|
||||
### ✅ Автоматически поддерживается:
|
||||
- **Все текстовые поля** в экранах воронки (title, subtitle, description)
|
||||
- **Info экраны** - описания
|
||||
- **Soulmate Portrait** - описания портрета
|
||||
- **Date экраны** - info сообщения
|
||||
- **Form экраны** - лейблы и placeholder
|
||||
- **Coupon экраны** - все текстовые поля купона
|
||||
|
||||
### 🔧 Как это работает:
|
||||
|
||||
#### 1. **Автоматическое обнаружение:**
|
||||
```typescript
|
||||
// Система автоматически проверяет каждый текст на наличие разметки
|
||||
import { hasTextMarkup } from "@/lib/text-markup";
|
||||
|
||||
if (hasTextMarkup("Ваш **идеальный** партнер")) {
|
||||
// Включает обработку разметки автоматически
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. **Универсальная обработка:**
|
||||
```typescript
|
||||
// Все компоненты Typography автоматически поддерживают разметку
|
||||
<Typography enableMarkup={hasTextMarkup(text)}>
|
||||
{text}
|
||||
</Typography>
|
||||
```
|
||||
|
||||
#### 3. **Превью в админке:**
|
||||
```typescript
|
||||
// В админке показывается живое превью разметки
|
||||
<MarkupPreview text="Ваш **идеальный** партнер" />
|
||||
```
|
||||
|
||||
## Примеры для разных типов экранов
|
||||
|
||||
### 📋 **Info экраны:**
|
||||
```json
|
||||
{
|
||||
"template": "info",
|
||||
"description": {
|
||||
"text": "Мы проанализировали **12 миллионов** анкет и нашли **идеальные совпадения** для вас!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 💖 **Soulmate Portrait:**
|
||||
```json
|
||||
{
|
||||
"template": "soulmate",
|
||||
"description": {
|
||||
"text": "Ваш **идеальный партнер** найден на основе **глубокого анализа** ваших ответов"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 📅 **Date экраны:**
|
||||
```json
|
||||
{
|
||||
"template": "date",
|
||||
"infoMessage": {
|
||||
"text": "Мы используем дату рождения для определения **знака зодиака** и **совместимости**"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 🎟️ **Coupon экраны:**
|
||||
```json
|
||||
{
|
||||
"template": "coupon",
|
||||
"coupon": {
|
||||
"title": { "text": "**94% скидка** только сегодня!" },
|
||||
"description": { "text": "Используйте промокод **HAIR50** и получите максимальную скидку" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Технические детали
|
||||
|
||||
### 🔧 **Компоненты системы:**
|
||||
|
||||
#### 1. **Утилиты (`/lib/text-markup.ts`):**
|
||||
- `parseTextMarkup()` - парсинг разметки в сегменты
|
||||
- `hasTextMarkup()` - проверка наличия разметки
|
||||
- `stripTextMarkup()` - удаление разметки
|
||||
|
||||
#### 2. **React компоненты (`/components/ui/MarkupText/`):**
|
||||
- `<MarkupText>` - рендеринг текста с разметкой
|
||||
- `<MarkupPreview>` - превью в админке
|
||||
- `useHasMarkup()` - React хук для проверки
|
||||
|
||||
#### 3. **Интеграция (`Typography.tsx`):**
|
||||
- Автоматическая активация при обнаружении разметки
|
||||
- Параметр `enableMarkup` для ручного управления
|
||||
- Совместимость с существующими стилями
|
||||
|
||||
### 📝 **Пример кода:**
|
||||
```tsx
|
||||
// Автоматическое использование
|
||||
<Typography>
|
||||
Ваш **идеальный** партнер найден!
|
||||
</Typography>
|
||||
|
||||
// Ручное управление
|
||||
<Typography enableMarkup={true}>
|
||||
Обычный текст с **выделением**
|
||||
</Typography>
|
||||
|
||||
// Прямое использование MarkupText
|
||||
<MarkupText as="h1" boldClassName="text-primary">
|
||||
**WitLab** - найди свою любовь!
|
||||
</MarkupText>
|
||||
```
|
||||
|
||||
## Лучшие практики
|
||||
|
||||
### ✅ **Хорошо:**
|
||||
- `"Ваш **идеальный** партнер найден!"` - выделение ключевых слов
|
||||
- `"**50%** скидка только сегодня"` - выделение цифр и акций
|
||||
- `"Поздравляем, **Анна**!"` - выделение имен
|
||||
|
||||
### ❌ **Избегайте:**
|
||||
- `"**Весь текст жирный**"` - потеря контраста
|
||||
- `"**Сл**ов**ом** **разб**ит**ые**"` - нечитаемость
|
||||
- `"****"` - пустые выделения
|
||||
|
||||
### 💡 **Советы:**
|
||||
1. **Выделяйте ключевые слова** - имена, проценты, важные понятия
|
||||
2. **Соблюдайте баланс** - не более 20% текста должно быть жирным
|
||||
3. **Тестируйте в превью** - используйте MarkupPreview в админке
|
||||
4. **Учитывайте контекст** - в заголовках выделение менее заметно
|
||||
|
||||
## Совместимость
|
||||
|
||||
- ✅ **React 18+**
|
||||
- ✅ **TypeScript 5+**
|
||||
- ✅ **Next.js 14+**
|
||||
- ✅ **Tailwind CSS**
|
||||
- ✅ **Обратная совместимость** - существующий текст работает без изменений
|
||||
216
README-ADMIN.md
216
README-ADMIN.md
@ -1,216 +0,0 @@
|
||||
# WitLab Funnel Admin - Полноценная админка с MongoDB
|
||||
|
||||
## Что реализовано
|
||||
|
||||
### ✅ База данных MongoDB
|
||||
- **Подключение через Mongoose** с автоматическим переподключением
|
||||
- **Модели для воронок** с полной валидацией структуры данных
|
||||
- **История изменений** для системы undo/redo
|
||||
- **Индексы для производительности** поиска и фильтрации
|
||||
|
||||
### ✅ API Routes
|
||||
- `GET /api/funnels` - список воронок с пагинацией и фильтрами
|
||||
- `POST /api/funnels` - создание новой воронки
|
||||
- `GET /api/funnels/[id]` - получение конкретной воронки
|
||||
- `PUT /api/funnels/[id]` - обновление воронки
|
||||
- `DELETE /api/funnels/[id]` - удаление воронки (только черновики)
|
||||
- `POST /api/funnels/[id]/duplicate` - дублирование воронки
|
||||
- `GET/POST /api/funnels/[id]/history` - работа с историей изменений
|
||||
- `GET /api/funnels/by-funnel-id/[funnelId]` - загрузка по funnel ID (для совместимости)
|
||||
|
||||
### ✅ Каталог воронок `/admin`
|
||||
- **Список всех воронок** с поиском, фильтрацией и сортировкой
|
||||
- **Создание новых воронок** с базовым шаблоном
|
||||
- **Дублирование существующих** воронок
|
||||
- **Удаление черновиков** (опубликованные можно только архивировать)
|
||||
- **Статистика использования** (просмотры, завершения)
|
||||
- **Статусы**: draft, published, archived
|
||||
|
||||
### ✅ Редактор воронок `/admin/builder/[id]`
|
||||
- **Полноценный билдер** интегрированный с существующей архитектурой
|
||||
- **Автосохранение** изменений в базу данных
|
||||
- **Система публикации** с контролем версий
|
||||
- **Топ бар** с информацией о воронке и кнопками действий
|
||||
- **Экспорт/импорт JSON** для резервного копирования
|
||||
|
||||
### ✅ Система undo/redo
|
||||
- **История действий** с глубиной до 50 шагов
|
||||
- **Базовые точки** при сохранении в БД (после сохранения нельзя откатить)
|
||||
- **Несохраненные изменения** отслеживаются отдельно
|
||||
- **Автоматическая очистка** старых записей истории
|
||||
|
||||
### ✅ Интеграция с существующим кодом
|
||||
- **Обратная совместимость** с JSON файлами
|
||||
- **Приоритет базы данных** при загрузке воронок
|
||||
- **Автоматическое увеличение статистики** при просмотрах
|
||||
- **Единый API** для всех компонентов системы
|
||||
|
||||
## Настройка окружения
|
||||
|
||||
### 1. MongoDB Connection
|
||||
Создайте `.env.local` файл:
|
||||
```bash
|
||||
# MongoDB
|
||||
MONGODB_URI=mongodb://localhost:27017/witlab-funnel
|
||||
# или для MongoDB Atlas:
|
||||
# MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/witlab-funnel
|
||||
|
||||
# Base URL (для server-side запросов)
|
||||
NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
||||
```
|
||||
|
||||
### 2. Установка MongoDB локально
|
||||
```bash
|
||||
# macOS (через Homebrew)
|
||||
brew install mongodb-community
|
||||
brew services start mongodb-community
|
||||
|
||||
# Или используйте MongoDB Atlas (облако)
|
||||
```
|
||||
|
||||
### 3. Запуск проекта
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
### Создание новой воронки
|
||||
1. Перейдите на `/admin`
|
||||
2. Нажмите "Создать воронку"
|
||||
3. Автоматически откроется билдер с базовым шаблоном
|
||||
4. Редактируйте экраны в правом сайдбаре
|
||||
5. Сохраняйте изменения кнопкой "Сохранить"
|
||||
6. Публикуйте готовую воронку кнопкой "Опубликовать"
|
||||
|
||||
### Редактирование существующей воронки
|
||||
1. В каталоге найдите нужную воронку
|
||||
2. Нажмите иконку "Редактировать" (карандаш)
|
||||
3. Внесите изменения в билдере
|
||||
4. Сохраните или опубликуйте
|
||||
|
||||
### Просмотр воронки
|
||||
1. Нажмите иконку "Просмотр" (глаз) в каталоге
|
||||
2. Или перейдите на `/{funnelId}` напрямую
|
||||
|
||||
### Дублирование воронки
|
||||
1. Нажмите иконку "Дублировать" (копия)
|
||||
2. Создастся копия со статусом "Черновик"
|
||||
3. Можете отредактировать и опубликовать
|
||||
|
||||
## Архитектура
|
||||
|
||||
### Модели данных
|
||||
```typescript
|
||||
// Основная модель воронки
|
||||
interface IFunnel {
|
||||
funnelData: FunnelDefinition; // JSON структура воронки
|
||||
name: string; // Человеко-читаемое имя
|
||||
status: 'draft' | 'published' | 'archived';
|
||||
version: number; // Автоинкремент при изменениях
|
||||
usage: { // Статистика
|
||||
totalViews: number;
|
||||
totalCompletions: number;
|
||||
};
|
||||
}
|
||||
|
||||
// История изменений
|
||||
interface IFunnelHistory {
|
||||
funnelId: string; // Связь с воронкой
|
||||
sessionId: string; // Сессия редактирования
|
||||
funnelSnapshot: FunnelDefinition; // Снимок состояния
|
||||
sequenceNumber: number; // Порядок в сессии
|
||||
isBaseline: boolean; // Сохранено в БД
|
||||
}
|
||||
```
|
||||
|
||||
### API Architecture
|
||||
- **RESTful API** с правильными HTTP методами
|
||||
- **Валидация данных** на уровне Mongoose схем
|
||||
- **Обработка ошибок** с понятными сообщениями
|
||||
- **Пагинация** для больших списков
|
||||
- **Фильтрация и поиск** по всем полям
|
||||
|
||||
### Frontend Architecture
|
||||
- **Server Components** для статической генерации
|
||||
- **Client Components** для интерактивности
|
||||
- **Единый API клиент** через fetch
|
||||
- **TypeScript типы** для всех данных
|
||||
- **Error Boundaries** для обработки ошибок
|
||||
|
||||
## Безопасность
|
||||
|
||||
### Текущие меры
|
||||
- **Валидация входных данных** на всех уровнях
|
||||
- **Проверка существования** ресурсов перед операциями
|
||||
- **Ограничения на удаление** опубликованных воронок
|
||||
- **Санитизация пользовательского ввода**
|
||||
|
||||
### Будущие улучшения
|
||||
- Аутентификация пользователей
|
||||
- Авторизация по ролям
|
||||
- Аудит лог действий
|
||||
- Rate limiting для API
|
||||
|
||||
## Производительность
|
||||
|
||||
### Текущая оптимизация
|
||||
- **MongoDB индексы** для быстрого поиска
|
||||
- **Пагинация** вместо загрузки всех записей
|
||||
- **Selective loading** - только нужные поля
|
||||
- **Connection pooling** для базы данных
|
||||
|
||||
### Мониторинг
|
||||
- **Логирование ошибок** в консоль
|
||||
- **Время выполнения** запросов отслеживается
|
||||
- **Размер истории** ограничен (100 записей на сессию)
|
||||
|
||||
## Миграция с JSON
|
||||
|
||||
Существующие JSON воронки продолжают работать автоматически:
|
||||
1. **Приоритет базы данных** - сначала поиск в MongoDB
|
||||
2. **Fallback на JSON** - если не найдено в базе
|
||||
3. **Импорт из JSON** - можно загрузить JSON в билдере
|
||||
4. **Экспорт в JSON** - для резервного копирования
|
||||
|
||||
## Roadmap
|
||||
|
||||
### Ближайшие планы
|
||||
- [x] Основная функциональность админки
|
||||
- [x] Система undo/redo
|
||||
- [x] Интеграция с существующим кодом
|
||||
- [ ] Аутентификация пользователей
|
||||
- [ ] Collaborative editing
|
||||
- [ ] Advanced аналитика
|
||||
|
||||
### Долгосрочные цели
|
||||
- [ ] Multi-tenant архитектура
|
||||
- [ ] A/B тестирование воронок
|
||||
- [ ] Интеграция с внешними сервисами
|
||||
- [ ] Mobile app для мониторинга
|
||||
|
||||
## Техническая поддержка
|
||||
|
||||
### Логи и отладка
|
||||
```bash
|
||||
# Проверка подключения к MongoDB
|
||||
curl http://localhost:3000/api/funnels
|
||||
|
||||
# Просмотр логов в консоли разработчика
|
||||
# MongoDB connection logs в терминале
|
||||
```
|
||||
|
||||
### Частые проблемы
|
||||
1. **MongoDB not connected** - проверьте MONGODB_URI в .env.local
|
||||
2. **API errors** - проверьте сетевое соединение
|
||||
3. **Build errors** - убедитесь что все зависимости установлены
|
||||
|
||||
### Контакты
|
||||
- GitHub Issues для багрепортов
|
||||
- Документация в `/docs/`
|
||||
- Комментарии в коде для сложных частей
|
||||
|
||||
---
|
||||
|
||||
**Полноценная админка с MongoDB готова к использованию! 🚀**
|
||||
Loading…
Reference in New Issue
Block a user