w-funnel/src/components/admin/builder/templates/TrialChoiceScreenConfig.tsx
2025-12-01 04:09:26 +03:00

194 lines
7.5 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 { useState } from "react";
import { ChevronDown, ChevronRight } from "lucide-react";
import { TextAreaInput } from "@/components/ui/TextAreaInput/TextAreaInput";
import type { TrialChoiceScreenDefinition, ArrowHintPosition, AccentedOptionSetting } from "@/lib/funnel/types";
import type { BuilderScreen } from "@/lib/admin/builder/types";
interface TrialChoiceScreenConfigProps {
screen: BuilderScreen & { template: "trialChoice" };
onUpdate: (updates: Partial<TrialChoiceScreenDefinition>) => void;
}
const ARROW_POSITION_OPTIONS: { value: ArrowHintPosition; label: string }[] = [
{ value: "bottom-right", label: "Снизу справа" },
{ value: "bottom-left", label: "Снизу слева" },
{ value: "top-right", label: "Сверху справа" },
{ value: "top-left", label: "Сверху слева" },
];
const ACCENTED_OPTION_OPTIONS: { value: AccentedOptionSetting; label: string }[] = [
{ value: "server", label: "С сервера (по умолчанию)" },
{ value: 1, label: "Вариант 1" },
{ value: 2, label: "Вариант 2" },
{ value: 3, label: "Вариант 3" },
{ value: 4, label: "Вариант 4" },
];
function CollapsibleSection({
title,
children,
defaultExpanded = false,
}: {
title: string;
children: React.ReactNode;
defaultExpanded?: boolean;
}) {
const storageKey = `trial-choice-section-${title.toLowerCase().replace(/\s+/g, "-")}`;
const [isExpanded, setIsExpanded] = useState(() => {
if (typeof window === "undefined") return defaultExpanded;
const stored = sessionStorage.getItem(storageKey);
return stored !== null ? JSON.parse(stored) : defaultExpanded;
});
const handleToggle = () => {
const newExpanded = !isExpanded;
setIsExpanded(newExpanded);
if (typeof window !== "undefined") {
sessionStorage.setItem(storageKey, JSON.stringify(newExpanded));
}
};
return (
<div className="space-y-3">
<button
type="button"
onClick={handleToggle}
className="flex w-full items-center gap-2 text-left text-sm font-medium text-foreground hover:text-primary transition-colors"
>
{isExpanded ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
{title}
</button>
{isExpanded && <div className="ml-6 space-y-3">{children}</div>}
</div>
);
}
export function TrialChoiceScreenConfig({
screen,
onUpdate,
}: TrialChoiceScreenConfigProps) {
const trialChoiceScreen = screen as TrialChoiceScreenDefinition;
const arrowHint = trialChoiceScreen.arrowHint;
// Для корректной работы с вариантами, передаем полный объект arrowHint
// чтобы diff/merge функции корректно обрабатывали изменения
const handleArrowHintTextChange = (text: string) => {
onUpdate({
arrowHint: {
text,
position: arrowHint?.position ?? "bottom-right",
},
});
};
const handleArrowHintPositionChange = (position: ArrowHintPosition) => {
onUpdate({
arrowHint: {
text: arrowHint?.text ?? "",
position,
},
});
};
return (
<div className="space-y-4">
<CollapsibleSection title="Подсказка со стрелкой" defaultExpanded={true}>
<div className="space-y-4 rounded-2xl border border-border/70 bg-background/80 p-4 shadow-sm">
{/* Текст подсказки */}
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
Текст подсказки
</label>
<TextAreaInput
value={arrowHint?.text ?? ""}
onChange={(event) => handleArrowHintTextChange(event.target.value)}
rows={3}
className="resize-y"
placeholder="Введите текст подсказки..."
/>
<div className="rounded-lg bg-muted/50 p-3 space-y-2">
<p className="text-xs font-medium text-muted-foreground">
Доступные переменные:
</p>
<div className="flex flex-wrap gap-2">
<code className="bg-background px-2 py-1 rounded text-xs border border-border">
{"{{maxTrialPrice}}"}
</code>
<span className="text-xs text-muted-foreground">
максимальная цена триала
</span>
</div>
<p className="text-[10px] text-muted-foreground mt-2">
Переменная автоматически заменяется на форматированную цену самого дорогого варианта триала.
</p>
</div>
</div>
{/* Позиция стрелки */}
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
Позиция стрелки
</label>
<select
className="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm"
value={arrowHint?.position ?? "bottom-right"}
onChange={(event) =>
handleArrowHintPositionChange(event.target.value as ArrowHintPosition)
}
>
{ARROW_POSITION_OPTIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
<p className="text-xs text-muted-foreground">
Определяет на какой угол сетки вариантов указывает стрелка.
При выборе позиции сверху стрелка и текст отображаются над сеткой.
</p>
</div>
</div>
</CollapsibleSection>
<CollapsibleSection title="Подсветка варианта" defaultExpanded={true}>
<div className="space-y-4 rounded-2xl border border-border/70 bg-background/80 p-4 shadow-sm">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
Подсвечиваемый вариант
</label>
<select
className="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm"
value={String(trialChoiceScreen.accentedOption ?? "server")}
onChange={(event) => {
const value = event.target.value;
const parsed = value === "server" ? "server" : parseInt(value, 10);
onUpdate({ accentedOption: parsed as AccentedOptionSetting });
}}
>
{ACCENTED_OPTION_OPTIONS.map((option) => (
<option key={String(option.value)} value={String(option.value)}>
{option.label}
</option>
))}
</select>
<p className="text-xs text-muted-foreground">
Выберите какой вариант триала будет подсвечен.
Если выбранный номер больше количества вариантов с сервера,
будет использована серверная логика.
</p>
</div>
</div>
</CollapsibleSection>
</div>
);
}