145 lines
4.6 KiB
TypeScript
145 lines
4.6 KiB
TypeScript
"use client";
|
||
|
||
import { useMemo } from "react";
|
||
import Image from "next/image";
|
||
import type { InfoScreenDefinition, DefaultTexts, FunnelAnswers } from "@/lib/funnel/types";
|
||
import { TemplateLayout } from "../layouts/TemplateLayout";
|
||
import { cn } from "@/lib/utils";
|
||
import { createTemplateLayoutProps } from "@/lib/funnel/templateHelpers";
|
||
import { substituteVariables } from "@/lib/funnel/variableSubstitution";
|
||
|
||
interface InfoTemplateProps {
|
||
screen: InfoScreenDefinition;
|
||
onContinue: () => void;
|
||
canGoBack: boolean;
|
||
onBack: () => void;
|
||
screenProgress?: { current: number; total: number };
|
||
defaultTexts?: DefaultTexts;
|
||
answers: FunnelAnswers;
|
||
}
|
||
|
||
export function InfoTemplate({
|
||
screen,
|
||
onContinue,
|
||
canGoBack,
|
||
onBack,
|
||
screenProgress,
|
||
defaultTexts,
|
||
answers,
|
||
}: InfoTemplateProps) {
|
||
// Подставляем переменные в title и subtitle
|
||
const processedScreen = useMemo(() => {
|
||
if (!screen.variables || screen.variables.length === 0) {
|
||
return screen;
|
||
}
|
||
|
||
return {
|
||
...screen,
|
||
title: {
|
||
...screen.title,
|
||
text: substituteVariables(screen.title.text, screen.variables, answers),
|
||
},
|
||
subtitle: screen.subtitle ? {
|
||
...screen.subtitle,
|
||
text: substituteVariables(screen.subtitle.text, screen.variables, answers),
|
||
} : screen.subtitle,
|
||
};
|
||
}, [screen, answers]);
|
||
|
||
const iconSizeClasses = useMemo(() => {
|
||
const size = processedScreen.icon?.size ?? "xl";
|
||
switch (size) {
|
||
case "sm":
|
||
return "text-4xl";
|
||
case "md":
|
||
return "text-5xl";
|
||
case "lg":
|
||
return "text-6xl";
|
||
case "xl":
|
||
default:
|
||
return "text-8xl";
|
||
}
|
||
}, [processedScreen.icon?.size]);
|
||
|
||
// Функция для проверки валидности URL
|
||
const isValidUrl = (value: string): boolean => {
|
||
if (!value || value.trim() === '') return false;
|
||
|
||
try {
|
||
new URL(value);
|
||
return true;
|
||
} catch {
|
||
// Проверяем относительные пути (начинаются с /) и API пути
|
||
return value.startsWith('/') || value.startsWith('/api/');
|
||
}
|
||
};
|
||
|
||
// Создаем иконку для передачи в childrenAboveTitle
|
||
const iconElement = processedScreen.icon ? (
|
||
<div className={cn("mb-8", processedScreen.icon.className)}>
|
||
{/* Если type не указан, определяем автоматически: URL = image, иначе emoji */}
|
||
{(processedScreen.icon.type === "emoji" || (!processedScreen.icon.type && !isValidUrl(processedScreen.icon.value))) ? (
|
||
<div className={cn(iconSizeClasses, "leading-none")}>
|
||
{processedScreen.icon.value}
|
||
</div>
|
||
) : (processedScreen.icon.value && isValidUrl(processedScreen.icon.value)) ? (
|
||
<Image
|
||
src={processedScreen.icon.value}
|
||
alt=""
|
||
width={
|
||
iconSizeClasses.includes("text-8xl") ? 128 :
|
||
iconSizeClasses.includes("text-6xl") ? 64 :
|
||
iconSizeClasses.includes("text-5xl") ? 48 : 36
|
||
}
|
||
height={
|
||
iconSizeClasses.includes("text-8xl") ? 128 :
|
||
iconSizeClasses.includes("text-6xl") ? 64 :
|
||
iconSizeClasses.includes("text-5xl") ? 48 : 36
|
||
}
|
||
className={cn("object-contain")}
|
||
unoptimized={processedScreen.icon.value.startsWith('/api/images/')}
|
||
onError={(e) => {
|
||
console.error('Preview image load error:', processedScreen.icon?.value, e);
|
||
}}
|
||
onLoad={() => {
|
||
console.log('Preview image loaded successfully:', processedScreen.icon?.value);
|
||
}}
|
||
/>
|
||
) : (
|
||
<div className={cn(iconSizeClasses, "leading-none text-muted-foreground flex items-center justify-center")}>
|
||
📷
|
||
</div>
|
||
)}
|
||
</div>
|
||
) : null;
|
||
|
||
const layoutProps = createTemplateLayoutProps(
|
||
processedScreen,
|
||
{ canGoBack, onBack },
|
||
screenProgress,
|
||
{
|
||
preset: "center",
|
||
actionButton: {
|
||
defaultText: defaultTexts?.nextButton || "Next",
|
||
disabled: false,
|
||
onClick: onContinue,
|
||
},
|
||
childrenAboveTitle: iconElement,
|
||
}
|
||
);
|
||
|
||
return (
|
||
<TemplateLayout {...layoutProps}>
|
||
{/* Пустые дети - весь контент теперь в заголовке, подзаголовке и иконке */}
|
||
<div className="w-full flex justify-center">
|
||
<div className={cn(
|
||
"w-full max-w-[320px] text-center",
|
||
processedScreen.icon ? "mt-[30px]" : "mt-[60px]"
|
||
)}>
|
||
{/* Дополнительный контент если нужен */}
|
||
</div>
|
||
</div>
|
||
</TemplateLayout>
|
||
);
|
||
}
|