From 22c6d513afdf2e6872c19901816670a0fc7d8e9e Mon Sep 17 00:00:00 2001 From: "dev.daminik00" Date: Fri, 26 Sep 2025 02:19:22 +0200 Subject: [PATCH] fix --- public/funnels/funnel-test.json | 125 +---------- src/app/[funnelId]/page.tsx | 6 +- .../admin/builder/BuilderPreview.tsx | 10 +- .../builder/templates/ListScreenConfig.tsx | 206 ++++++++++++++++++ .../builder/templates/TemplateConfig.tsx | 22 +- .../builder/templates/TextScreenConfig.tsx | 198 ----------------- .../admin/builder/templates/index.ts | 2 +- src/components/funnel/FunnelRuntime.tsx | 161 +++++++++----- .../funnel/templates/CouponTemplate.tsx | 68 ++---- .../funnel/templates/DateTemplate.tsx | 72 ++---- .../funnel/templates/FormTemplate.tsx | 100 +++------ .../funnel/templates/InfoTemplate.tsx | 61 ++---- .../funnel/templates/ListTemplate.tsx | 66 ++---- .../funnel/templates/TextTemplate.tsx | 97 --------- src/components/layout/Header/Header.tsx | 10 +- .../layout/LayoutQuestion/LayoutQuestion.tsx | 2 +- .../RadioAnswersList/RadioAnswersList.tsx | 9 +- .../SelectAnswersList/SelectAnswersList.tsx | 9 +- src/lib/funnel/mappers.tsx | 139 ++---------- src/lib/funnel/types.ts | 68 +----- 20 files changed, 478 insertions(+), 953 deletions(-) create mode 100644 src/components/admin/builder/templates/ListScreenConfig.tsx delete mode 100644 src/components/admin/builder/templates/TextScreenConfig.tsx delete mode 100644 src/components/funnel/templates/TextTemplate.tsx diff --git a/public/funnels/funnel-test.json b/public/funnels/funnel-test.json index e2e1284..bebe57b 100644 --- a/public/funnels/funnel-test.json +++ b/public/funnels/funnel-test.json @@ -9,45 +9,6 @@ "nextButton": "Next", "continueButton": "Continue" }, - "colorPalette": { - "text": { - "primary": "#1E293B", - "secondary": "#475569", - "muted": "#64748B", - "accent": "#3B82F6", - "success": "#10B981", - "error": "#EF4444", - "warning": "#F59E0B" - }, - "background": { - "primary": "#FFFFFF", - "secondary": "#F8FAFC", - "accent": "#EFF6FF", - "success": "#ECFDF5", - "error": "#FEF2F2", - "warning": "#FFFBEB" - }, - "button": { - "primary": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", - "primaryText": "#FFFFFF", - "secondary": "#F1F5F9", - "secondaryText": "#334155", - "disabled": "#E2E8F0", - "disabledText": "#94A3B8" - }, - "border": { - "primary": "#E2E8F0", - "accent": "#3B82F6", - "success": "#10B981", - "error": "#EF4444" - }, - "shadow": { - "light": "rgba(0, 0, 0, 0.05)", - "medium": "rgba(0, 0, 0, 0.1)", - "heavy": "rgba(0, 0, 0, 0.15)", - "colored": "rgba(59, 130, 246, 0.3)" - } - }, "screens": [ { "id": "intro-welcome", @@ -188,23 +149,20 @@ "maxLength": "Максимум ${maxLength} символов", "invalidFormat": "Неверный формат" }, - "bottomActionButton": { - "text": "Continue" - }, "navigation": { "defaultNextScreenId": "statistics-text" } }, { "id": "statistics-text", - "template": "text", + "template": "info", "title": { "text": "Which best represents your hair loss and goals?", "font": "manrope", "weight": "bold", "align": "center" }, - "content": { + "description": { "text": "По нашей статистике 51 % женщин Овнов доверяются эмоциям. Но одной чувствительности мало. Мы покажем, какие качества второй половинки дадут тепло и уверенность, и изобразим её портрет.", "font": "inter", "weight": "medium", @@ -285,13 +243,6 @@ { "id": "analysis-target", "template": "list", - "header": { - "progress": { - "current": 6, - "total": 15, - "label": "6 of 15" - } - }, "title": { "text": "Кого анализируем?", "font": "manrope", @@ -367,13 +318,6 @@ { "id": "current-partner-age", "template": "list", - "header": { - "progress": { - "current": 4, - "total": 9, - "label": "4 of 9" - } - }, "title": { "text": "Возраст текущего партнера", "font": "manrope", @@ -423,13 +367,6 @@ { "id": "crush-age", "template": "list", - "header": { - "progress": { - "current": 4, - "total": 9, - "label": "4 of 9" - } - }, "title": { "text": "Возраст человека, который нравится", "font": "manrope", @@ -460,6 +397,9 @@ } ] }, + "bottomActionButton": { + "show": false + }, "navigation": { "rules": [ { @@ -479,13 +419,6 @@ { "id": "ex-partner-age", "template": "list", - "header": { - "progress": { - "current": 4, - "total": 9, - "label": "4 of 9" - } - }, "title": { "text": "Возраст бывшего", "font": "manrope", @@ -535,13 +468,6 @@ { "id": "future-partner-age", "template": "list", - "header": { - "progress": { - "current": 4, - "total": 9, - "label": "4 of 9" - } - }, "title": { "text": "Возраст будущего партнера", "font": "manrope", @@ -591,13 +517,6 @@ { "id": "age-refine", "template": "list", - "header": { - "progress": { - "current": 5, - "total": 9, - "label": "5 of 9" - } - }, "title": { "text": "Уточните чуть точнее", "font": "manrope", @@ -631,13 +550,6 @@ { "id": "partner-ethnicity", "template": "list", - "header": { - "progress": { - "current": 6, - "total": 9, - "label": "6 of 9" - } - }, "title": { "text": "Этническая принадлежность твоей второй половинки?", "font": "manrope", @@ -687,13 +599,6 @@ { "id": "partner-eyes", "template": "list", - "header": { - "progress": { - "current": 7, - "total": 9, - "label": "7 of 9" - } - }, "title": { "text": "Что из этого «про глаза»?", "font": "manrope", @@ -735,13 +640,6 @@ { "id": "partner-hair-length", "template": "list", - "header": { - "progress": { - "current": 8, - "total": 9, - "label": "8 of 9" - } - }, "title": { "text": "Выберите длину волос", "font": "manrope", @@ -775,20 +673,13 @@ { "id": "burnout-support", "template": "list", - "header": { - "progress": { - "current": 9, - "total": 9, - "label": "9 of 9" - } - }, "title": { "text": "Когда ты выгораешь, тебе нужно чтобы партнёр...", "font": "manrope", "weight": "bold" }, "list": { - "selectionType": "single", + "selectionType": "multi", "options": [ { "id": "reassure", @@ -812,6 +703,10 @@ } ] }, + "bottomActionButton": { + "text": "Continue", + "show": false + }, "navigation": { "defaultNextScreenId": "special-offer" } diff --git a/src/app/[funnelId]/page.tsx b/src/app/[funnelId]/page.tsx index 8bdfd1f..f26c135 100644 --- a/src/app/[funnelId]/page.tsx +++ b/src/app/[funnelId]/page.tsx @@ -3,13 +3,13 @@ import { notFound, redirect } from "next/navigation"; import { loadFunnelDefinition } from "@/lib/funnel/loadFunnelDefinition"; interface FunnelRootPageProps { - params: { + params: Promise<{ funnelId: string; - }; + }>; } export default async function FunnelRootPage({ params }: FunnelRootPageProps) { - const { funnelId } = params; + const { funnelId } = await params; let funnel; try { diff --git a/src/components/admin/builder/BuilderPreview.tsx b/src/components/admin/builder/BuilderPreview.tsx index 194eb9d..bb720f2 100644 --- a/src/components/admin/builder/BuilderPreview.tsx +++ b/src/components/admin/builder/BuilderPreview.tsx @@ -6,10 +6,9 @@ import { ListTemplate } from "@/components/funnel/templates/ListTemplate"; import { InfoTemplate } from "@/components/funnel/templates/InfoTemplate"; import { DateTemplate } from "@/components/funnel/templates/DateTemplate"; import { FormTemplate } from "@/components/funnel/templates/FormTemplate"; -import { TextTemplate } from "@/components/funnel/templates/TextTemplate"; import { CouponTemplate } from "@/components/funnel/templates/CouponTemplate"; import { useBuilderSelectedScreen } from "@/lib/admin/builder/context"; -import type { ListScreenDefinition, InfoScreenDefinition, DateScreenDefinition, FormScreenDefinition, TextScreenDefinition, CouponScreenDefinition } from "@/lib/funnel/types"; +import type { ListScreenDefinition, InfoScreenDefinition, DateScreenDefinition, FormScreenDefinition, CouponScreenDefinition } from "@/lib/funnel/types"; export function BuilderPreview() { const selectedScreen = useBuilderSelectedScreen(); @@ -94,13 +93,6 @@ export function BuilderPreview() { /> ); - case "text": - return ( - - ); case "coupon": return ( diff --git a/src/components/admin/builder/templates/ListScreenConfig.tsx b/src/components/admin/builder/templates/ListScreenConfig.tsx new file mode 100644 index 0000000..df33df6 --- /dev/null +++ b/src/components/admin/builder/templates/ListScreenConfig.tsx @@ -0,0 +1,206 @@ +"use client"; + +import { TextInput } from "@/components/ui/TextInput/TextInput"; +import { Button } from "@/components/ui/button"; +import { Trash2, Plus } from "lucide-react"; +import type { ListScreenDefinition, ListOptionDefinition, SelectionType } from "@/lib/funnel/types"; +import type { BuilderScreen } from "@/lib/admin/builder/types"; + +interface ListScreenConfigProps { + screen: BuilderScreen & { template: "list" }; + onUpdate: (updates: Partial) => void; +} + +export function ListScreenConfig({ screen, onUpdate }: ListScreenConfigProps) { + const listScreen = screen as ListScreenDefinition & { position: { x: number; y: number } }; + + const handleTitleChange = (text: string) => { + onUpdate({ + title: { + ...listScreen.title, + text, + font: listScreen.title?.font || "manrope", + weight: listScreen.title?.weight || "bold", + align: listScreen.title?.align || "left", + } + }); + }; + + const handleSubtitleChange = (text: string) => { + onUpdate({ + subtitle: text ? { + ...listScreen.subtitle, + text, + font: listScreen.subtitle?.font || "inter", + weight: listScreen.subtitle?.weight || "medium", + color: listScreen.subtitle?.color || "muted", + align: listScreen.subtitle?.align || "left", + } : undefined + }); + }; + + const handleSelectionTypeChange = (selectionType: SelectionType) => { + onUpdate({ + list: { + ...listScreen.list, + selectionType, + } + }); + }; + + const handleOptionChange = (index: number, field: keyof ListOptionDefinition, value: string | boolean) => { + const newOptions = [...listScreen.list.options]; + newOptions[index] = { + ...newOptions[index], + [field]: value, + }; + + onUpdate({ + list: { + ...listScreen.list, + options: newOptions, + } + }); + }; + + const handleAddOption = () => { + const newOptions = [...listScreen.list.options]; + newOptions.push({ + id: `option-${Date.now()}`, + label: "New Option", + }); + + onUpdate({ + list: { + ...listScreen.list, + options: newOptions, + } + }); + }; + + const handleRemoveOption = (index: number) => { + const newOptions = listScreen.list.options.filter((_, i) => i !== index); + + onUpdate({ + list: { + ...listScreen.list, + options: newOptions, + } + }); + }; + + const handleBottomActionButtonChange = (text: string) => { + onUpdate({ + list: { + ...listScreen.list, + bottomActionButton: text ? { + text, + show: true, + } : undefined, + } + }); + }; + + return ( +
+ {/* Title Configuration */} +
+ + handleTitleChange(e.target.value)} + /> +
+ + {/* Subtitle Configuration */} +
+ + handleSubtitleChange(e.target.value)} + /> +
+ + {/* Selection Type */} +
+ +
+ + +
+
+ + {/* Options */} +
+
+ + +
+ +
+ {listScreen.list.options.map((option, index) => ( +
+
+ handleOptionChange(index, "id", e.target.value)} + /> +
+
+ handleOptionChange(index, "label", e.target.value)} + /> +
+ +
+ ))} +
+
+ + {/* Bottom Action Button */} +
+ + handleBottomActionButtonChange(e.target.value)} + /> +
+ {listScreen.list.selectionType === "multi" + ? "Multi selection always shows a button" + : "Single selection: empty = auto-advance, filled = manual button"} +
+
+
+ ); +} diff --git a/src/components/admin/builder/templates/TemplateConfig.tsx b/src/components/admin/builder/templates/TemplateConfig.tsx index 0f5a306..3472c2e 100644 --- a/src/components/admin/builder/templates/TemplateConfig.tsx +++ b/src/components/admin/builder/templates/TemplateConfig.tsx @@ -4,10 +4,10 @@ import { InfoScreenConfig } from "./InfoScreenConfig"; import { DateScreenConfig } from "./DateScreenConfig"; import { CouponScreenConfig } from "./CouponScreenConfig"; import { FormScreenConfig } from "./FormScreenConfig"; -import { TextScreenConfig } from "./TextScreenConfig"; +import { ListScreenConfig } from "./ListScreenConfig"; import type { BuilderScreen } from "@/lib/admin/builder/types"; -import type { ScreenDefinition, InfoScreenDefinition, DateScreenDefinition, CouponScreenDefinition, FormScreenDefinition, TextScreenDefinition } from "@/lib/funnel/types"; +import type { ScreenDefinition, InfoScreenDefinition, DateScreenDefinition, CouponScreenDefinition, FormScreenDefinition, ListScreenDefinition } from "@/lib/funnel/types"; interface TemplateConfigProps { screen: BuilderScreen; @@ -50,22 +50,12 @@ export function TemplateConfig({ screen, onUpdate }: TemplateConfigProps) { /> ); - case "text": - return ( - ) => void} - /> - ); - case "list": return ( -
-
- List template configuration is available in the existing sidebar. - This is a legacy template that will be updated soon. -
-
+ ) => void} + /> ); default: diff --git a/src/components/admin/builder/templates/TextScreenConfig.tsx b/src/components/admin/builder/templates/TextScreenConfig.tsx deleted file mode 100644 index 6129090..0000000 --- a/src/components/admin/builder/templates/TextScreenConfig.tsx +++ /dev/null @@ -1,198 +0,0 @@ -"use client"; - -import { TextInput } from "@/components/ui/TextInput/TextInput"; -import type { TextScreenDefinition } from "@/lib/funnel/types"; -import type { BuilderScreen } from "@/lib/admin/builder/types"; - -interface TextScreenConfigProps { - screen: BuilderScreen & { template: "text" }; - onUpdate: (updates: Partial) => void; -} - -export function TextScreenConfig({ screen, onUpdate }: TextScreenConfigProps) { - const textScreen = screen as TextScreenDefinition & { position: { x: number; y: number } }; - - return ( -
- {/* Title Configuration */} -
- - onUpdate({ - title: { - ...textScreen.title, - text: e.target.value, - font: textScreen.title?.font || "manrope", - weight: textScreen.title?.weight || "bold", - align: textScreen.title?.align || "center", - } - })} - /> - -
- - - - - -
-
- - {/* Content Configuration */} -
- -