w-funnel/src/lib/funnel/screenRenderer.tsx
2025-09-28 22:48:50 +02:00

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;
}