"use client"; import React from "react"; import { TextInput } from "@/components/ui/TextInput/TextInput"; import { Button } from "@/components/ui/button"; import { MediaUpload } from "@/components/admin/builder/forms/MediaUpload"; import { Plus, Trash2 } from "lucide-react"; import type { BuilderScreen } from "@/lib/admin/builder/types"; import type { SoulmatePortraitScreenDefinition } from "@/lib/funnel/types"; interface SoulmatePortraitScreenConfigProps { screen: BuilderScreen & { template: "soulmate" }; onUpdate: (updates: Partial) => void; } export function SoulmatePortraitScreenConfig({ screen, onUpdate }: SoulmatePortraitScreenConfigProps) { type Delivered = NonNullable; type Avatar = NonNullable[number]; type DeliveredText = NonNullable; const updateDescription = (updates: Partial) => { onUpdate({ description: screen.description ? { ...screen.description, ...updates, } : { text: "", ...updates }, }); }; const updateDelivered = ( updates: Partial> ) => { const base = screen.soulmatePortraitsDelivered ?? {}; const nextDelivered = { ...base, ...updates }; // Важно для вариантов: сохраняем undefined значения! // Система вариантов использует undefined для отслеживания удаленных полей onUpdate({ soulmatePortraitsDelivered: nextDelivered as NonNullable, }); }; const updateDeliveredText = ( updates: Partial ) => { const currentText = (screen.soulmatePortraitsDelivered?.text ?? { text: "" }) as DeliveredText; const nextText = { ...currentText, ...(updates as object) } as DeliveredText; updateDelivered({ text: nextText }); }; const addAvatar = () => { const avatars = screen.soulmatePortraitsDelivered?.avatars ?? []; updateDelivered({ avatars: [...avatars, { src: "", alt: "", fallbackText: "" }] }); }; const removeAvatar = (index: number) => { const avatars = screen.soulmatePortraitsDelivered?.avatars ?? []; updateDelivered({ avatars: avatars.filter((_, i) => i !== index) }); }; const updateAvatar = ( index: number, updates: Partial ) => { const avatars = screen.soulmatePortraitsDelivered?.avatars ?? []; const next = avatars.map((a, i) => (i === index ? { ...a, ...updates } : a)); updateDelivered({ avatars: next }); }; const addTextListItem = () => { const items = screen.textList?.items ?? []; onUpdate({ textList: { items: [...items, { text: "" }] } }); }; const removeTextListItem = (index: number) => { const items = screen.textList?.items ?? []; onUpdate({ textList: { items: items.filter((_, i) => i !== index) } }); }; const updateTextListItem = ( index: number, updates: Partial["items"][number]> ) => { const items = screen.textList?.items ?? []; const next = items.map((it, i) => (i === index ? { ...it, ...updates } : it)); onUpdate({ textList: { items: next } }); }; return (

Описание портрета

updateDescription({ text: e.target.value })} />

Блок доставленных портретов

Основное медиа (изображение/видео/GIF) updateDelivered({ mediaUrl: url, mediaType: type, image: null as unknown as undefined })} onMediaRemove={() => updateDelivered({ mediaUrl: null as unknown as undefined, mediaType: null as unknown as undefined, image: null as unknown as undefined })} funnelId={screen.id} />

Поддержка: изображения (JPG, PNG, WebP), видео (MP4, MOV), GIF с автовоспроизведением без звука

updateDeliveredText({ text: e.target.value })} />

Аватары

{(screen.soulmatePortraitsDelivered?.avatars ?? []).map((avatar, index) => (
Аватар {index + 1}
Изображение (только фото, не видео) updateAvatar(index, { src: url })} onMediaRemove={() => updateAvatar(index, { src: "" })} funnelId={screen.id} />
updateAvatar(index, { alt: e.target.value })} /> updateAvatar(index, { fallbackText: e.target.value })} />
Можно указать изображение или fallback текст (или оба).
))}
{(screen.soulmatePortraitsDelivered?.avatars ?? []).length === 0 && (

Нет аватаров

)}

Список текстов

{(screen.textList?.items ?? []).map((item, index) => (
updateTextListItem(index, { text: e.target.value })} />
))}
{(screen.textList?.items ?? []).length === 0 && (

Пока нет элементов

)}

Информация

• PrivacyTermsConsent настраивается через bottomActionButton.showPrivacyTermsConsent

💡 Назначение экрана

Экран “Soulmate Portrait” предназначен для отображения результатов анализа совместимости или характеристик идеального партнера на основе ответов пользователя в воронке.

); }