244 lines
7.2 KiB
TypeScript
244 lines
7.2 KiB
TypeScript
"use client";
|
|
|
|
import type { JSX } from "react";
|
|
|
|
import {
|
|
ListTemplate,
|
|
InfoTemplate,
|
|
DateTemplate,
|
|
CouponTemplate,
|
|
FormTemplate,
|
|
EmailTemplate,
|
|
LoadersTemplate,
|
|
SoulmatePortraitTemplate,
|
|
} from "@/components/funnel/templates";
|
|
import type {
|
|
ListScreenDefinition,
|
|
DateScreenDefinition,
|
|
FormScreenDefinition,
|
|
CouponScreenDefinition,
|
|
InfoScreenDefinition,
|
|
EmailScreenDefinition,
|
|
LoadersScreenDefinition,
|
|
SoulmatePortraitScreenDefinition,
|
|
ScreenDefinition,
|
|
DefaultTexts,
|
|
} from "@/lib/funnel/types";
|
|
|
|
export interface ScreenRenderProps {
|
|
screen: ScreenDefinition;
|
|
selectedOptionIds: string[];
|
|
onSelectionChange: (ids: string[]) => void;
|
|
onContinue: () => void;
|
|
canGoBack: boolean;
|
|
onBack: () => void;
|
|
screenProgress: { current: number; total: number };
|
|
defaultTexts?: DefaultTexts;
|
|
}
|
|
|
|
export type TemplateRenderer = (props: ScreenRenderProps) => JSX.Element;
|
|
|
|
const TEMPLATE_REGISTRY: Record<ScreenDefinition["template"], TemplateRenderer> = {
|
|
info: ({ screen, onContinue, canGoBack, onBack, screenProgress, defaultTexts }) => {
|
|
const infoScreen = screen as InfoScreenDefinition;
|
|
|
|
return (
|
|
<InfoTemplate
|
|
screen={infoScreen}
|
|
onContinue={onContinue}
|
|
canGoBack={canGoBack}
|
|
onBack={onBack}
|
|
screenProgress={screenProgress}
|
|
defaultTexts={defaultTexts}
|
|
/>
|
|
);
|
|
},
|
|
date: ({ screen, selectedOptionIds, onSelectionChange, onContinue, canGoBack, onBack, screenProgress, defaultTexts }) => {
|
|
const dateScreen = screen as DateScreenDefinition;
|
|
|
|
// For date screens, we store date components as array: [month, day, year]
|
|
const currentDateArray = selectedOptionIds;
|
|
const selectedDate = {
|
|
month: currentDateArray[0] || "",
|
|
day: currentDateArray[1] || "",
|
|
year: currentDateArray[2] || "",
|
|
};
|
|
|
|
const handleDateChange = (date: { month?: string; day?: string; year?: string }) => {
|
|
const dateArray = [date.month || "", date.day || "", date.year || ""];
|
|
onSelectionChange(dateArray);
|
|
};
|
|
|
|
return (
|
|
<DateTemplate
|
|
screen={dateScreen}
|
|
selectedDate={selectedDate}
|
|
onDateChange={handleDateChange}
|
|
onContinue={onContinue}
|
|
canGoBack={canGoBack}
|
|
onBack={onBack}
|
|
screenProgress={screenProgress}
|
|
defaultTexts={defaultTexts}
|
|
/>
|
|
);
|
|
},
|
|
form: ({ screen, selectedOptionIds, onSelectionChange, onContinue, canGoBack, onBack, screenProgress, defaultTexts }) => {
|
|
const formScreen = screen as FormScreenDefinition;
|
|
|
|
// For form screens, we store form data as JSON string in the first element
|
|
const formDataJson = selectedOptionIds[0] || "{}";
|
|
let formData: Record<string, string> = {};
|
|
|
|
try {
|
|
formData = JSON.parse(formDataJson);
|
|
} catch {
|
|
formData = {};
|
|
}
|
|
|
|
const handleFormDataChange = (data: Record<string, string>) => {
|
|
const dataJson = JSON.stringify(data);
|
|
onSelectionChange([dataJson]);
|
|
};
|
|
|
|
return (
|
|
<FormTemplate
|
|
screen={formScreen}
|
|
formData={formData}
|
|
onFormDataChange={handleFormDataChange}
|
|
onContinue={onContinue}
|
|
canGoBack={canGoBack}
|
|
onBack={onBack}
|
|
screenProgress={screenProgress}
|
|
defaultTexts={defaultTexts}
|
|
/>
|
|
);
|
|
},
|
|
coupon: ({ screen, onContinue, canGoBack, onBack, screenProgress, defaultTexts }) => {
|
|
const couponScreen = screen as CouponScreenDefinition;
|
|
|
|
return (
|
|
<CouponTemplate
|
|
screen={couponScreen}
|
|
onContinue={onContinue}
|
|
canGoBack={canGoBack}
|
|
onBack={onBack}
|
|
screenProgress={screenProgress}
|
|
defaultTexts={defaultTexts}
|
|
/>
|
|
);
|
|
},
|
|
list: ({
|
|
screen,
|
|
selectedOptionIds,
|
|
onSelectionChange,
|
|
onContinue,
|
|
canGoBack,
|
|
onBack,
|
|
screenProgress,
|
|
defaultTexts,
|
|
}) => {
|
|
const listScreen = screen as ListScreenDefinition;
|
|
const isSelectionEmpty = selectedOptionIds.length === 0;
|
|
|
|
// Используем только общую кнопку экрана
|
|
const bottomActionButton = listScreen.bottomActionButton;
|
|
const isButtonDisabled = bottomActionButton?.show === false;
|
|
|
|
// Простая логика: кнопка есть если не отключена (show: false)
|
|
const hasActionButton = !isButtonDisabled;
|
|
|
|
// Правильная логика приоритетов для текста кнопки:
|
|
// 1. bottomActionButton.text (настройка экрана)
|
|
// 2. defaultTexts.nextButton (глобальная настройка воронки)
|
|
// 3. "Next" (хардкод fallback)
|
|
const buttonText = bottomActionButton?.text || defaultTexts?.nextButton || "Next";
|
|
|
|
const actionDisabled = hasActionButton && isSelectionEmpty;
|
|
|
|
return (
|
|
<ListTemplate
|
|
screen={listScreen}
|
|
selectedOptionIds={selectedOptionIds}
|
|
onSelectionChange={onSelectionChange}
|
|
actionButtonProps={hasActionButton
|
|
? {
|
|
children: buttonText,
|
|
disabled: actionDisabled,
|
|
onClick: actionDisabled ? undefined : onContinue,
|
|
}
|
|
: undefined}
|
|
canGoBack={canGoBack}
|
|
onBack={onBack}
|
|
screenProgress={screenProgress}
|
|
/>
|
|
);
|
|
},
|
|
email: ({ screen, selectedOptionIds, onSelectionChange, onContinue, canGoBack, onBack, screenProgress, defaultTexts }) => {
|
|
const emailScreen = screen as EmailScreenDefinition;
|
|
|
|
// For email screens, we store email as single string in first element
|
|
const selectedEmail = selectedOptionIds[0] || "";
|
|
|
|
const handleEmailChange = (email: string) => {
|
|
onSelectionChange([email]);
|
|
};
|
|
|
|
return (
|
|
<EmailTemplate
|
|
screen={emailScreen}
|
|
selectedEmail={selectedEmail}
|
|
onEmailChange={handleEmailChange}
|
|
onContinue={onContinue}
|
|
canGoBack={canGoBack}
|
|
onBack={onBack}
|
|
screenProgress={screenProgress}
|
|
defaultTexts={defaultTexts}
|
|
/>
|
|
);
|
|
},
|
|
loaders: ({ screen, onContinue, canGoBack, onBack, screenProgress, defaultTexts }) => {
|
|
const loadersScreen = screen as LoadersScreenDefinition;
|
|
|
|
return (
|
|
<LoadersTemplate
|
|
screen={loadersScreen}
|
|
onContinue={onContinue}
|
|
canGoBack={canGoBack}
|
|
onBack={onBack}
|
|
screenProgress={screenProgress}
|
|
defaultTexts={defaultTexts}
|
|
/>
|
|
);
|
|
},
|
|
soulmate: ({ screen, onContinue, canGoBack, onBack, screenProgress, defaultTexts }) => {
|
|
const soulmateScreen = screen as SoulmatePortraitScreenDefinition;
|
|
|
|
return (
|
|
<SoulmatePortraitTemplate
|
|
screen={soulmateScreen}
|
|
onContinue={onContinue}
|
|
canGoBack={canGoBack}
|
|
onBack={onBack}
|
|
screenProgress={screenProgress}
|
|
defaultTexts={defaultTexts}
|
|
/>
|
|
);
|
|
},
|
|
};
|
|
|
|
export function renderScreen(props: ScreenRenderProps): JSX.Element {
|
|
const renderer = TEMPLATE_REGISTRY[props.screen.template];
|
|
if (!renderer) {
|
|
throw new Error(`Unsupported template: ${props.screen.template}`);
|
|
}
|
|
return renderer(props);
|
|
}
|
|
|
|
export function getTemplateRenderer(screen: ScreenDefinition): TemplateRenderer {
|
|
const renderer = TEMPLATE_REGISTRY[screen.template];
|
|
if (!renderer) {
|
|
throw new Error(`Unsupported template: ${screen.template}`);
|
|
}
|
|
return renderer;
|
|
}
|