feat(lead-forms): public form matches builder preview; live unsaved preview in new tab

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-07 14:25:52 +03:30
co-authored by Cursor
parent 678cb87de7
commit f5b6a47f3c
2 changed files with 151 additions and 39 deletions
+42 -3
View File
@@ -13,6 +13,7 @@ import type {
LeadFormFieldType,
LeadFormMapTo,
LeadFormSubmissionDto,
PublicLeadFormDto,
LeadStageDto,
OrgUserDto,
PipelineDto,
@@ -437,9 +438,47 @@ async function copyText(text: string) {
ok.value = 'کپی شد.';
}
const previewDraft = computed<PublicLeadFormDto | null>(() => {
if (!form.value) return null;
return {
name: name.value.trim() || 'عنوان فرم',
description: description.value.trim() || null,
thankYouMessage: thankYouMessage.value.trim() || null,
redirectUrl: null,
submitLabel: submitLabel.value.trim() || 'ارسال درخواست',
submitColor: submitColor.value,
hasCover: form.value.hasCover,
coverPath: coverSrc.value || null,
fields: fields.value.map((f) => ({
key: f.key,
label: f.label,
fieldType: f.fieldType,
required: f.required,
placeholder: f.placeholder || null,
options: f.options,
})),
};
});
function previewKey() {
return `lead-form-preview:${form.value?.publicToken ?? ''}`;
}
function writePreviewDraft() {
if (!previewDraft.value) return;
try {
localStorage.setItem(previewKey(), JSON.stringify(previewDraft.value));
} catch {
/* storage full / disabled — preview falls back to saved version */
}
}
watch(previewDraft, writePreviewDraft, { deep: true });
function openPreview() {
if (!form.value?.publicPath) return;
window.open(form.value.publicPath, '_blank', 'noopener');
writePreviewDraft();
window.open(`${form.value.publicPath}?preview=1`, '_blank', 'noopener');
}
function toggleAssignee(uid: string) {
@@ -515,7 +554,7 @@ onMounted(load);
<template v-for="f in fields" :key="f.key">
<p v-if="f.fieldType === 'heading'" class="heading">{{ f.label }}</p>
<label v-else>
{{ f.label }}
<span>{{ f.label }}<span v-if="f.required" class="req">*</span></span>
<textarea v-if="f.fieldType === 'textarea'" rows="3" :placeholder="f.placeholder || ''" disabled />
<select v-else-if="f.fieldType === 'select'" disabled>
<option>{{ f.placeholder || 'انتخاب کنید' }}</option>
@@ -845,7 +884,7 @@ onMounted(load);
<template v-for="f in fields" :key="'d-' + f.key">
<p v-if="f.fieldType === 'heading'" class="heading wide">{{ f.label }}</p>
<label v-else :class="{ wide: f.fieldType === 'textarea' || f.fieldType === 'checkbox' }">
{{ f.label }}
<span>{{ f.label }}<span v-if="f.required" class="req">*</span></span>
<textarea v-if="f.fieldType === 'textarea'" rows="3" :placeholder="f.placeholder || ''" disabled />
<select v-else-if="f.fieldType === 'select'" disabled>
<option>{{ f.placeholder || 'انتخاب کنید' }}</option>