w-funnel/src/components/funnel/templates/InfoTemplate/InfoTemplate.tsx
dev.daminik00 aa956adebb add funnel
2025-10-06 02:41:09 +02:00

145 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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