add payment image variant

This commit is contained in:
dev.daminik00 2025-10-21 02:53:57 +02:00
parent 36c45f3521
commit 30875ef4a7
4 changed files with 376 additions and 305 deletions

View File

@ -2325,10 +2325,28 @@
"navigation": {
"rules": [],
"defaultNextScreenId": "specialoffer",
"isEndScreen": true,
"onBackScreenId": "specialoffer"
"isEndScreen": false
},
"variants": [],
"variants": [
{
"conditions": [
{
"screenId": "partner-gender",
"operator": "includesAny",
"optionIds": [
"male"
]
}
],
"overrides": {
"unlockYourSketch": {
"image": {
"src": "/trial-payment/portrait-male.jpg"
}
}
}
}
],
"headerBlock": {
"text": {
"text": "⚠️ Your sketch expires soon!"
@ -2776,10 +2794,6 @@
"cornerRadius": "3xl",
"showPrivacyTermsConsent": false
},
"navigation": {
"rules": [],
"isEndScreen": true
},
"variants": [],
"text": {
"title": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

View File

@ -1,18 +1,64 @@
"use client";
import React from "react";
import React, { useState } from "react";
import type { BuilderScreen } from "@/lib/admin/builder/types";
import type { TrialPaymentScreenDefinition } from "@/lib/funnel/types";
import { TextInput } from "@/components/ui/TextInput/TextInput";
import { TextAreaInput } from "@/components/ui/TextAreaInput/TextAreaInput";
import { Button } from "@/components/ui/button";
import { Trash } from "lucide-react";
import { Trash, ChevronDown, ChevronRight } from "lucide-react";
interface TrialPaymentScreenConfigProps {
screen: BuilderScreen & { template: "trialPayment" };
onUpdate: (updates: Partial<TrialPaymentScreenDefinition>) => void;
}
function CollapsibleSection({
title,
children,
defaultExpanded = false,
}: {
title: string;
children: React.ReactNode;
defaultExpanded?: boolean;
}) {
const storageKey = `trial-payment-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="space-y-3">{children}</div>}
</div>
);
}
export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScreenConfigProps) {
const updateHeaderBlock = (updates: Partial<NonNullable<TrialPaymentScreenDefinition["headerBlock"]>>) => {
onUpdate({ headerBlock: { ...screen.headerBlock, ...updates } });
@ -55,9 +101,9 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
};
return (
<div className="space-y-6">
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Header Block</h4>
<div className="space-y-4">
{/* Header Block */}
<CollapsibleSection title="Header Block" defaultExpanded={true}>
<div className="space-y-3">
<TextInput
label="Текст"
@ -71,10 +117,195 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
onChange={(e) => updateHeaderBlock({ timerSeconds: Number(e.target.value) })}
/>
</div>
</div>
</CollapsibleSection>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Reviews</h4>
{/* Unlock Your Sketch */}
<CollapsibleSection title="Unlock Your Sketch" defaultExpanded={true}>
<div className="space-y-3">
<TextInput label="Заголовок" value={screen.unlockYourSketch?.title?.text ?? ""} onChange={(e) => updateUnlock({ title: { text: e.target.value } })} />
<TextInput label="Подзаголовок" value={screen.unlockYourSketch?.subtitle?.text ?? ""} onChange={(e) => updateUnlock({ subtitle: { text: e.target.value } })} />
<TextInput label="Изображение" value={screen.unlockYourSketch?.image?.src ?? ""} onChange={(e) => updateUnlock({ image: { src: e.target.value } })} />
<TextInput label="Текст на блюре" value={screen.unlockYourSketch?.blur?.text?.text ?? ""} onChange={(e) => updateUnlock({ blur: { ...(screen.unlockYourSketch?.blur ?? {}), text: { text: e.target.value }, icon: "lock" } as NonNullable<TrialPaymentScreenDefinition["unlockYourSketch"]>["blur"] })} />
<TextInput label="Текст кнопки" value={screen.unlockYourSketch?.buttonText ?? ""} onChange={(e) => updateUnlock({ buttonText: e.target.value })} />
</div>
</CollapsibleSection>
{/* Social Proof */}
<CollapsibleSection title="Social Proof">
<div className="space-y-3">
<TextInput
label="Joined Today - Count"
value={screen.joinedToday?.count?.text ?? ""}
onChange={(e) => onUpdate({ joinedToday: { ...screen.joinedToday, count: { text: e.target.value } } })}
/>
<TextInput
label="Joined Today - Text"
value={screen.joinedToday?.text?.text ?? ""}
onChange={(e) => onUpdate({ joinedToday: { ...screen.joinedToday, text: { text: e.target.value } } })}
/>
<TextInput
label="Trusted By Over"
value={screen.trustedByOver?.text?.text ?? ""}
onChange={(e) => onUpdate({ trustedByOver: { text: { text: e.target.value } } })}
/>
</div>
</CollapsibleSection>
{/* Finding The One Guide */}
<CollapsibleSection title="Finding The One Guide">
<div className="space-y-3">
<TextInput
label="Emoji"
value={screen.findingOneGuide?.header?.emoji?.text ?? ""}
onChange={(e) => onUpdate({ findingOneGuide: { ...screen.findingOneGuide, header: { ...(screen.findingOneGuide?.header ?? {}), emoji: { text: e.target.value } } } })}
/>
<TextInput
label="Title"
value={screen.findingOneGuide?.header?.title?.text ?? ""}
onChange={(e) => onUpdate({ findingOneGuide: { ...screen.findingOneGuide, header: { ...(screen.findingOneGuide?.header ?? {}), title: { text: e.target.value } } } })}
/>
<TextAreaInput
label="Text"
rows={4}
value={screen.findingOneGuide?.text?.text ?? ""}
onChange={(e) => onUpdate({ findingOneGuide: { ...screen.findingOneGuide, text: { text: e.target.value } } })}
/>
<TextInput
label="Blur text"
value={screen.findingOneGuide?.blur?.text?.text ?? ""}
onChange={(e) => onUpdate({ findingOneGuide: { ...screen.findingOneGuide, blur: { ...(screen.findingOneGuide?.blur ?? {}), text: { text: e.target.value }, icon: "lock" } } })}
/>
</div>
</CollapsibleSection>
{/* Try For Days */}
<CollapsibleSection title="Try For Days">
<div className="space-y-3">
<TextInput
label="Title"
value={screen.tryForDays?.title?.text ?? ""}
onChange={(e) => onUpdate({ tryForDays: { ...screen.tryForDays, title: { text: e.target.value } } })}
/>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">Items</span>
<Button
type="button"
className="h-7 px-2 text-xs"
onClick={() => {
const current = screen.tryForDays?.textList?.items ?? [];
const items = [...current, { text: "" }];
onUpdate({ tryForDays: { ...screen.tryForDays, textList: { items } } });
}}
>
Добавить
</Button>
</div>
{(screen.tryForDays?.textList?.items ?? []).map((it, idx) => (
<div key={idx} className="space-y-2 border border-border/50 rounded-lg bg-muted/5 p-3">
<TextAreaInput
label={`Item #${idx + 1}`}
rows={2}
value={it.text}
onChange={(e) => {
const current = screen.tryForDays?.textList?.items ?? [];
const items = current.map((v, i) => (i === idx ? { ...v, text: e.target.value } : v));
onUpdate({ tryForDays: { ...screen.tryForDays, textList: { items } } });
}}
/>
<div className="flex justify-end">
<Button
type="button"
variant="ghost"
className="h-8 w-8 p-0 text-red-500 hover:text-red-600"
onClick={() => {
const current = screen.tryForDays?.textList?.items ?? [];
const items = current.filter((_, i) => i !== idx);
onUpdate({ tryForDays: { ...screen.tryForDays, textList: { items } } });
}}
aria-label="Удалить элемент"
>
<Trash className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
</div>
</CollapsibleSection>
{/* Pricing & Payment */}
<CollapsibleSection title="Pricing & Payment">
<div className="space-y-3">
<TextInput
label="Coupon title"
value={screen.totalPrice?.couponContainer?.title?.text ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: { ...(screen.totalPrice?.couponContainer ?? {}), title: { text: e.target.value } }, priceContainer: screen.totalPrice?.priceContainer } })}
/>
<TextInput
label="Coupon button text"
value={screen.totalPrice?.couponContainer?.buttonText ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: { ...(screen.totalPrice?.couponContainer ?? {}), buttonText: e.target.value }, priceContainer: screen.totalPrice?.priceContainer } })}
/>
<TextInput
label="Price title"
value={screen.totalPrice?.priceContainer?.title?.text ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: screen.totalPrice?.couponContainer ?? { title: { text: "" }, buttonText: "" }, priceContainer: { ...(screen.totalPrice?.priceContainer ?? {}), title: { text: e.target.value } } } })}
/>
<TextInput
label="Price"
value={screen.totalPrice?.priceContainer?.price?.text ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: screen.totalPrice?.couponContainer ?? { title: { text: "" }, buttonText: "" }, priceContainer: { ...(screen.totalPrice?.priceContainer ?? {}), price: { text: e.target.value } } } })}
/>
<TextInput
label="Old price"
value={screen.totalPrice?.priceContainer?.oldPrice?.text ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: screen.totalPrice?.couponContainer ?? { title: { text: "" }, buttonText: "" }, priceContainer: { ...(screen.totalPrice?.priceContainer ?? {}), oldPrice: { text: e.target.value } } } })}
/>
<TextInput
label="Discount"
value={screen.totalPrice?.priceContainer?.discount?.text ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: screen.totalPrice?.couponContainer ?? { title: { text: "" }, buttonText: "" }, priceContainer: { ...(screen.totalPrice?.priceContainer ?? {}), discount: { text: e.target.value } } } })}
/>
<div className="pt-3 border-t border-border/30">
<span className="text-xs font-medium text-muted-foreground mb-2 block">Payment Buttons</span>
{(screen.paymentButtons?.buttons ?? []).map((b, i) => (
<div key={i} className="space-y-2 mb-2 border border-border/50 rounded-lg bg-muted/5 p-3">
<TextInput label={`Текст #${i + 1}`} value={b.text} onChange={(e) => updatePaymentButtons(i, "text", e.target.value)} />
<TextInput label="Иконка (pay|google|card)" value={("icon" in b ? (b as { icon?: "pay"|"google"|"card" }).icon ?? "" : "")} onChange={(e) => updatePaymentButtons(i, "icon", e.target.value)} />
<label className="text-xs flex items-center gap-2"><input type="checkbox" checked={("primary" in b ? (b as { primary?: boolean }).primary ?? false : false)} onChange={(e) => updatePaymentButtons(i, "primary", e.target.checked)} /> Primary</label>
</div>
))}
</div>
</div>
</CollapsibleSection>
{/* Trust & Guarantees */}
<CollapsibleSection title="Trust & Guarantees">
<div className="space-y-3">
<TextInput
label="Money Back Guarantee - Title"
value={screen.moneyBackGuarantee?.title?.text ?? ""}
onChange={(e) => onUpdate({ moneyBackGuarantee: { ...screen.moneyBackGuarantee, title: { text: e.target.value } } })}
/>
<TextAreaInput
label="Money Back Guarantee - Text"
rows={2}
value={screen.moneyBackGuarantee?.text?.text ?? ""}
onChange={(e) => onUpdate({ moneyBackGuarantee: { ...screen.moneyBackGuarantee, text: { text: e.target.value } } })}
/>
<TextAreaInput
label="Policy Text"
rows={3}
value={screen.policy?.text?.text ?? ""}
onChange={(e) => onUpdate({ policy: { text: { text: e.target.value } } })}
/>
</div>
</CollapsibleSection>
{/* Reviews */}
<CollapsibleSection title="Reviews">
<div className="space-y-3">
<TextInput
label="Title"
@ -100,7 +331,7 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
</Button>
</div>
{(screen.reviews?.items ?? []).map((r, idx) => (
<div key={idx} className="space-y-2 border border-border/60 rounded-md p-3">
<div key={idx} className="space-y-2 border border-border/50 rounded-lg bg-muted/5 p-3">
<TextInput
label={`Review #${idx + 1} name`}
value={r.name.text}
@ -187,10 +418,10 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
))}
</div>
</div>
</div>
</CollapsibleSection>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Common Questions</h4>
{/* Common Questions */}
<CollapsibleSection title="Common Questions">
<div className="space-y-3">
<TextInput
label="Title"
@ -213,7 +444,7 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
</Button>
</div>
{(screen.commonQuestions?.items ?? []).map((q, idx) => (
<div key={idx} className="grid grid-cols-1 gap-2 border border-border/60 rounded-md p-3">
<div key={idx} className="grid grid-cols-1 gap-2 border border-border/50 rounded-lg bg-muted/5 p-3">
<TextInput
label={`Question #${idx + 1}`}
value={q.question}
@ -252,10 +483,10 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
))}
</div>
</div>
</div>
</CollapsibleSection>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Progress To See Soulmate</h4>
{/* Progress & Steps */}
<CollapsibleSection title="Progress To See Soulmate">
<div className="space-y-3">
<TextInput
label="Title"
@ -279,10 +510,10 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
onChange={(e) => onUpdate({ progressToSeeSoulmate: { ...screen.progressToSeeSoulmate, rightText: { text: e.target.value } } })}
/>
</div>
</div>
</CollapsibleSection>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Steps To See Soulmate</h4>
{/* Steps To See Soulmate */}
<CollapsibleSection title="Steps To See Soulmate">
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">Steps</span>
@ -303,7 +534,7 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
</div>
{(screen.stepsToSeeSoulmate?.steps ?? []).map((step, idx) => (
<div key={idx} className="grid grid-cols-1 gap-2 border border-border/60 rounded-md p-3">
<div key={idx} className="grid grid-cols-1 gap-2 border border-border/50 rounded-lg bg-muted/5 p-3">
<div className="space-y-3">
<TextInput
label={`Step #${idx + 1} title`}
@ -365,38 +596,10 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
</div>
))}
</div>
</div>
</CollapsibleSection>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Money Back Guarantee</h4>
<div className="space-y-3">
<TextInput
label="Title"
value={screen.moneyBackGuarantee?.title?.text ?? ""}
onChange={(e) => onUpdate({ moneyBackGuarantee: { ...screen.moneyBackGuarantee, title: { text: e.target.value } } })}
/>
<TextAreaInput
label="Text"
value={screen.moneyBackGuarantee?.text?.text ?? ""}
onChange={(e) => onUpdate({ moneyBackGuarantee: { ...screen.moneyBackGuarantee, text: { text: e.target.value } } })}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Policy</h4>
<div className="grid grid-cols-1 gap-3">
<TextAreaInput
label="Text"
rows={3}
value={screen.policy?.text?.text ?? ""}
onChange={(e) => onUpdate({ policy: { text: { text: e.target.value } } })}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Users&apos; Portraits</h4>
{/* Users' Portraits */}
<CollapsibleSection title="Users' Portraits">
<div className="space-y-3">
<TextInput
label="Title"
@ -408,55 +611,55 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
value={screen.usersPortraits?.buttonText ?? ""}
onChange={(e) => onUpdate({ usersPortraits: { ...screen.usersPortraits, buttonText: e.target.value } })}
/>
</div>
<div className="space-y-2 mt-2">
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">Images</span>
<Button
type="button"
className="h-7 px-2 text-xs"
onClick={() => {
const current = screen.usersPortraits?.images ?? [];
const images = [...current, { src: "" }];
onUpdate({ usersPortraits: { ...screen.usersPortraits, images } });
}}
>
Добавить
</Button>
</div>
{(screen.usersPortraits?.images ?? []).map((img, idx) => (
<div key={idx} className="space-y-2">
<TextInput
label={`Image #${idx + 1} src`}
value={img.src}
onChange={(e) => {
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">Images</span>
<Button
type="button"
className="h-7 px-2 text-xs"
onClick={() => {
const current = screen.usersPortraits?.images ?? [];
const images = current.map((v, i) => (i === idx ? { ...v, src: e.target.value } : v));
const images = [...current, { src: "" }];
onUpdate({ usersPortraits: { ...screen.usersPortraits, images } });
}}
/>
<div className="flex justify-end">
<Button
type="button"
variant="ghost"
className="h-8 w-8 p-0 text-red-500 hover:text-red-600"
onClick={() => {
>
Добавить
</Button>
</div>
{(screen.usersPortraits?.images ?? []).map((img, idx) => (
<div key={idx} className="space-y-2 border border-border/50 rounded-lg bg-muted/5 p-3">
<TextInput
label={`Image #${idx + 1} src`}
value={img.src}
onChange={(e) => {
const current = screen.usersPortraits?.images ?? [];
const images = current.filter((_, i) => i !== idx);
const images = current.map((v, i) => (i === idx ? { ...v, src: e.target.value } : v));
onUpdate({ usersPortraits: { ...screen.usersPortraits, images } });
}}
aria-label="Удалить изображение"
>
<Trash className="h-4 w-4" />
</Button>
/>
<div className="flex justify-end">
<Button
type="button"
variant="ghost"
className="h-8 w-8 p-0 text-red-500 hover:text-red-600"
onClick={() => {
const current = screen.usersPortraits?.images ?? [];
const images = current.filter((_, i) => i !== idx);
onUpdate({ usersPortraits: { ...screen.usersPortraits, images } });
}}
aria-label="Удалить изображение"
>
<Trash className="h-4 w-4" />
</Button>
</div>
</div>
</div>
))}
))}
</div>
</div>
</div>
</CollapsibleSection>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Joined Today With Avatars</h4>
{/* Joined Today With Avatars */}
<CollapsibleSection title="Joined Today With Avatars">
<div className="space-y-3">
<TextInput
label="Count"
@ -468,86 +671,30 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
value={screen.joinedTodayWithAvatars?.text?.text ?? ""}
onChange={(e) => onUpdate({ joinedTodayWithAvatars: { ...screen.joinedTodayWithAvatars, text: { text: e.target.value } } })}
/>
</div>
<div className="space-y-2 mt-2">
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">Avatars</span>
<Button
type="button"
className="h-7 px-2 text-xs"
onClick={() => {
const current = screen.joinedTodayWithAvatars?.avatars?.images ?? [];
const images = [...current, { src: "" }];
onUpdate({ joinedTodayWithAvatars: { ...screen.joinedTodayWithAvatars, avatars: { images } } });
}}
>
Добавить
</Button>
</div>
{(screen.joinedTodayWithAvatars?.avatars?.images ?? []).map((img, idx) => (
<div key={idx} className="space-y-2">
<TextInput
label={`Avatar #${idx + 1} src`}
value={img.src}
onChange={(e) => {
const current = screen.joinedTodayWithAvatars?.avatars?.images ?? [];
const images = current.map((v, i) => (i === idx ? { ...v, src: e.target.value } : v));
onUpdate({ joinedTodayWithAvatars: { ...screen.joinedTodayWithAvatars, avatars: { images } } });
}}
/>
<div className="flex justify-end">
<Button
type="button"
variant="ghost"
className="h-8 w-8 p-0 text-red-500 hover:text-red-600"
onClick={() => {
const current = screen.joinedTodayWithAvatars?.avatars?.images ?? [];
const images = current.filter((_, i) => i !== idx);
onUpdate({ joinedTodayWithAvatars: { ...screen.joinedTodayWithAvatars, avatars: { images } } });
}}
aria-label="Удалить аватар"
>
<Trash className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
</div>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Try For Days</h4>
<div className="space-y-3">
<TextInput
label="Title"
value={screen.tryForDays?.title?.text ?? ""}
onChange={(e) => onUpdate({ tryForDays: { ...screen.tryForDays, title: { text: e.target.value } } })}
/>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">Items</span>
<span className="text-xs text-muted-foreground">Avatars</span>
<Button
type="button"
className="h-7 px-2 text-xs"
onClick={() => {
const current = screen.tryForDays?.textList?.items ?? [];
const items = [...current, { text: "" }];
onUpdate({ tryForDays: { ...screen.tryForDays, textList: { items } } });
const current = screen.joinedTodayWithAvatars?.avatars?.images ?? [];
const images = [...current, { src: "" }];
onUpdate({ joinedTodayWithAvatars: { ...screen.joinedTodayWithAvatars, avatars: { images } } });
}}
>
Добавить
</Button>
</div>
{(screen.tryForDays?.textList?.items ?? []).map((it, idx) => (
<div key={idx} className="space-y-2">
<TextAreaInput
label={`Item #${idx + 1}`}
rows={2}
value={it.text}
{(screen.joinedTodayWithAvatars?.avatars?.images ?? []).map((img, idx) => (
<div key={idx} className="space-y-2 border border-border/50 rounded-lg bg-muted/5 p-3">
<TextInput
label={`Avatar #${idx + 1} src`}
value={img.src}
onChange={(e) => {
const current = screen.tryForDays?.textList?.items ?? [];
const items = current.map((v, i) => (i === idx ? { ...v, text: e.target.value } : v));
onUpdate({ tryForDays: { ...screen.tryForDays, textList: { items } } });
const current = screen.joinedTodayWithAvatars?.avatars?.images ?? [];
const images = current.map((v, i) => (i === idx ? { ...v, src: e.target.value } : v));
onUpdate({ joinedTodayWithAvatars: { ...screen.joinedTodayWithAvatars, avatars: { images } } });
}}
/>
<div className="flex justify-end">
@ -556,11 +703,11 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
variant="ghost"
className="h-8 w-8 p-0 text-red-500 hover:text-red-600"
onClick={() => {
const current = screen.tryForDays?.textList?.items ?? [];
const items = current.filter((_, i) => i !== idx);
onUpdate({ tryForDays: { ...screen.tryForDays, textList: { items } } });
const current = screen.joinedTodayWithAvatars?.avatars?.images ?? [];
const images = current.filter((_, i) => i !== idx);
onUpdate({ joinedTodayWithAvatars: { ...screen.joinedTodayWithAvatars, avatars: { images } } });
}}
aria-label="Удалить элемент"
aria-label="Удалить аватар"
>
<Trash className="h-4 w-4" />
</Button>
@ -569,149 +716,45 @@ export function TrialPaymentScreenConfig({ screen, onUpdate }: TrialPaymentScree
))}
</div>
</div>
</div>
</CollapsibleSection>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Total Price</h4>
{/* FAQ & Support */}
<CollapsibleSection title="FAQ & Support">
<div className="space-y-3">
<TextInput
label="Coupon title"
value={screen.totalPrice?.couponContainer?.title?.text ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: { ...(screen.totalPrice?.couponContainer ?? {}), title: { text: e.target.value } }, priceContainer: screen.totalPrice?.priceContainer } })}
/>
<TextInput
label="Coupon button text"
value={screen.totalPrice?.couponContainer?.buttonText ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: { ...(screen.totalPrice?.couponContainer ?? {}), buttonText: e.target.value }, priceContainer: screen.totalPrice?.priceContainer } })}
/>
<TextInput
label="Price title"
value={screen.totalPrice?.priceContainer?.title?.text ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: screen.totalPrice?.couponContainer ?? { title: { text: "" }, buttonText: "" }, priceContainer: { ...(screen.totalPrice?.priceContainer ?? {}), title: { text: e.target.value } } } })}
/>
<TextInput
label="Price"
value={screen.totalPrice?.priceContainer?.price?.text ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: screen.totalPrice?.couponContainer ?? { title: { text: "" }, buttonText: "" }, priceContainer: { ...(screen.totalPrice?.priceContainer ?? {}), price: { text: e.target.value } } } })}
/>
<TextInput
label="Old price"
value={screen.totalPrice?.priceContainer?.oldPrice?.text ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: screen.totalPrice?.couponContainer ?? { title: { text: "" }, buttonText: "" }, priceContainer: { ...(screen.totalPrice?.priceContainer ?? {}), oldPrice: { text: e.target.value } } } })}
/>
<TextInput
label="Discount"
value={screen.totalPrice?.priceContainer?.discount?.text ?? ""}
onChange={(e) => onUpdate({ totalPrice: { couponContainer: screen.totalPrice?.couponContainer ?? { title: { text: "" }, buttonText: "" }, priceContainer: { ...(screen.totalPrice?.priceContainer ?? {}), discount: { text: e.target.value } } } })}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Joined Today</h4>
<div className="space-y-3">
<TextInput
label="Count"
value={screen.joinedToday?.count?.text ?? ""}
onChange={(e) => onUpdate({ joinedToday: { ...screen.joinedToday, count: { text: e.target.value } } })}
/>
<TextInput
label="Text"
value={screen.joinedToday?.text?.text ?? ""}
onChange={(e) => onUpdate({ joinedToday: { ...screen.joinedToday, text: { text: e.target.value } } })}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Trusted By Over</h4>
<div className="space-y-3">
<TextInput
label="Text"
value={screen.trustedByOver?.text?.text ?? ""}
onChange={(e) => onUpdate({ trustedByOver: { text: { text: e.target.value } } })}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Finding The One Guide</h4>
<div className="space-y-3">
<TextInput
label="Emoji"
value={screen.findingOneGuide?.header?.emoji?.text ?? ""}
onChange={(e) => onUpdate({ findingOneGuide: { ...screen.findingOneGuide, header: { ...(screen.findingOneGuide?.header ?? {}), emoji: { text: e.target.value } } } })}
/>
<TextInput
label="Title"
value={screen.findingOneGuide?.header?.title?.text ?? ""}
onChange={(e) => onUpdate({ findingOneGuide: { ...screen.findingOneGuide, header: { ...(screen.findingOneGuide?.header ?? {}), title: { text: e.target.value } } } })}
/>
<TextAreaInput
label="Text"
value={screen.findingOneGuide?.text?.text ?? ""}
onChange={(e) => onUpdate({ findingOneGuide: { ...screen.findingOneGuide, text: { text: e.target.value } } })}
/>
<TextInput
label="Blur text"
value={screen.findingOneGuide?.blur?.text?.text ?? ""}
onChange={(e) => onUpdate({ findingOneGuide: { ...screen.findingOneGuide, blur: { ...(screen.findingOneGuide?.blur ?? {}), text: { text: e.target.value }, icon: "lock" } } })}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Unlock Your Sketch</h4>
<div className="space-y-3">
<TextInput label="Заголовок" value={screen.unlockYourSketch?.title?.text ?? ""} onChange={(e) => updateUnlock({ title: { text: e.target.value } })} />
<TextInput label="Подзаголовок" value={screen.unlockYourSketch?.subtitle?.text ?? ""} onChange={(e) => updateUnlock({ subtitle: { text: e.target.value } })} />
<TextInput label="Изображение" value={screen.unlockYourSketch?.image?.src ?? ""} onChange={(e) => updateUnlock({ image: { src: e.target.value } })} />
<TextInput label="Текст на блюре" value={screen.unlockYourSketch?.blur?.text?.text ?? ""} onChange={(e) => updateUnlock({ blur: { ...(screen.unlockYourSketch?.blur ?? {}), text: { text: e.target.value }, icon: "lock" } as NonNullable<TrialPaymentScreenDefinition["unlockYourSketch"]>["blur"] })} />
<TextInput label="Текст кнопки" value={screen.unlockYourSketch?.buttonText ?? ""} onChange={(e) => updateUnlock({ buttonText: e.target.value })} />
</div>
</div>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Payment Buttons</h4>
{(screen.paymentButtons?.buttons ?? []).map((b, i) => (
<div key={i} className="space-y-2 mb-2">
<TextInput label={`Текст #${i + 1}`} value={b.text} onChange={(e) => updatePaymentButtons(i, "text", e.target.value)} />
<TextInput label="Иконка (pay|google|card)" value={("icon" in b ? (b as { icon?: "pay"|"google"|"card" }).icon ?? "" : "")} onChange={(e) => updatePaymentButtons(i, "icon", e.target.value)} />
<label className="text-xs flex items-center gap-2"><input type="checkbox" checked={("primary" in b ? (b as { primary?: boolean }).primary ?? false : false)} onChange={(e) => updatePaymentButtons(i, "primary", e.target.checked)} /> Primary</label>
</div>
))}
</div>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Footer / Contacts</h4>
<div className="space-y-3">
<TextInput label="Email" value={screen.footer?.contacts?.email?.text ?? screen.footer?.contacts?.email?.href ?? ""} onChange={(e) => updateFooterContacts("email", { href: e.target.value, text: e.target.value })} />
<TextAreaInput label="Адрес" value={screen.footer?.contacts?.address?.text ?? ""} onChange={(e) => updateFooterContacts("address", { text: e.target.value })} />
</div>
</div>
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Still Have Questions</h4>
<div className="space-y-3">
<TextInput
label="Title"
label="Still Have Questions - Title"
value={screen.stillHaveQuestions?.title?.text ?? ""}
onChange={(e) => onUpdate({ stillHaveQuestions: { ...screen.stillHaveQuestions, title: { text: e.target.value } } })}
/>
<TextInput
label="Action button"
label="Action button text"
value={screen.stillHaveQuestions?.actionButtonText ?? ""}
onChange={(e) => onUpdate({ stillHaveQuestions: { ...screen.stillHaveQuestions, actionButtonText: e.target.value } })}
/>
<TextInput
label="Contact button"
label="Contact button text"
value={screen.stillHaveQuestions?.contactButtonText ?? ""}
onChange={(e) => onUpdate({ stillHaveQuestions: { ...screen.stillHaveQuestions, contactButtonText: e.target.value } })}
/>
</div>
</div>
</CollapsibleSection>
{/* Footer */}
<CollapsibleSection title="Footer">
<div className="space-y-3">
<TextInput
label="Email"
value={screen.footer?.contacts?.email?.text ?? screen.footer?.contacts?.email?.href ?? ""}
onChange={(e) => updateFooterContacts("email", { href: e.target.value, text: e.target.value })}
/>
<TextAreaInput
label="Address"
rows={2}
value={screen.footer?.contacts?.address?.text ?? ""}
onChange={(e) => updateFooterContacts("address", { text: e.target.value })}
/>
</div>
</CollapsibleSection>
</div>
);
}

View File

@ -2333,10 +2333,28 @@ export const BAKED_FUNNELS: Record<string, FunnelDefinition> = {
"navigation": {
"rules": [],
"defaultNextScreenId": "specialoffer",
"isEndScreen": true,
"onBackScreenId": "specialoffer"
"isEndScreen": false
},
"variants": [],
"variants": [
{
"conditions": [
{
"screenId": "partner-gender",
"operator": "includesAny",
"optionIds": [
"male"
]
}
],
"overrides": {
"unlockYourSketch": {
"image": {
"src": "/trial-payment/portrait-male.jpg"
}
}
}
}
],
"headerBlock": {
"text": {
"text": "⚠️ Your sketch expires soon!"
@ -2784,10 +2802,6 @@ export const BAKED_FUNNELS: Record<string, FunnelDefinition> = {
"cornerRadius": "3xl",
"showPrivacyTermsConsent": false
},
"navigation": {
"rules": [],
"isEndScreen": true
},
"variants": [],
"text": {
"title": {