כאב טיפוסי: מנהל מבקש להוסיף שדה "זמן נוח לשיחה" לטופס המשוב. המפתח עורך את התבנית, מעדכן את הוולידציה, בודק בסביבת staging — 4 שעות מבוזבזות. שינויים כאלה קורים מדי שבוע, ומסיחים את הדעת מפיתוח הליבה. כתוצאה מכך, החברה מפסידה עד 16 שעות בחודש רק על שינויי טפסים. בפרויקט טיפוסי עם תריסר טפסים, שינויים כאלה הופכים למעגל אינסופי של אישורים ופריסות.
בונה טפסים מוכן מסוג drag-and-drop מבטל את הבעיה הזו ברמת הארכיטקטורה. פיתחנו פתרון שמאפשר לעובדים לא-טכניים ליצור ולשנות טפסים דרך ממשק אינטואיטיבי. הבונה מורכב משלושה רכיבים עצמאיים: Builder (עורך React), Renderer (רכיב תצוגה) ו-Backend (API לאחסון סכמות ותשובות). סכמת הטופס היא סכמת JSON שהרנדרר מפרש. זה נותן גמישות מלאה ללא שינויי קוד בעת הוספת סוג שדה חדש. להלן דוגמה לסכמה כזו.
{
"id": "uuid-v4",
"title": "Заявка на обратный звонок",
"description": "Мы перезвоним в течение 30 минут",
"settings": {
"submit_label": "Отправить заявку",
"success_message": "Спасибо! Мы свяжемся с вами.",
"redirect_url": null,
"notify_emails": ["[email protected]"],
"allow_multiple_submissions": false
},
"fields": [
{
"id": "field_1",
"type": "text",
"label": "Имя",
"placeholder": "Введите ваше имя",
"required": true,
"validation": {
"min_length": 2,
"max_length": 100
}
},
{
"id": "field_2",
"type": "phone",
"label": "Телефон",
"required": true,
"validation": {
"pattern": "^\\+?[\\d\\s\\-\\(\\)]{7,20}$"
}
},
{
"id": "field_3",
"type": "select",
"label": "Удобное время звонка",
"required": false,
"options": [
{
"value": "morning",
"label": "9:00 – 12:00"
},
{
"value": "afternoon",
"label": "12:00 – 17:00"
},
{
"value": "evening",
"label": "17:00 – 20:00"
}
]
},
{
"id": "field_4",
"type": "conditional_group",
"condition": {
"field": "field_3",
"operator": "equals",
"value": "evening"
},
"fields": [
{
"id": "field_4_1",
"type": "checkbox",
"label": "Подтверждаю, что звонок после 17:00 мне удобен",
"required": true
}
]
}
]
} למה JSON Schema?
סכמת JSON היא מקור האמת היחיד לכל הרכיבים. ה-Builder יוצר אותה, ה-Renderer קורא ומציג אותה, וה-Backend מאמת ומאחסן תשובות. ארכיטקטורה זו מאפשרת הוספת סוגי שדות חדשים ללא שינוי קוד frontend או backend — פשוט מרחיבים את הסכמה. זהו הבדל מרכזי מפתרונות מותאמים אישית שבהם כל שדה מקודד ישירות בתבנית.
מה אנחנו פותרים
- קידוד ידני של כל טופס — לוקח עד 4 שעות לכל שינוי. הבונה שלנו מקצר זאת ל-30 דקות — מהיר ב-87% מהדרך הישנה. עבור צוות שמבצע 4 שינויים בחודש, זה חוסך 14 שעות של זמן מפתח, שווה ערך ל-$700 בחודש במחיר של $50 לשעה.
- מורכבות בתחזוקת שדות מותאמים אישית — הוספת סוג חדש דורשת שינויי קוד בכל הרמות. סכמת JSON מאפשרת הוספת שדות ללא מעורבות מפתח, ומקצרת את זמן הפריסה מיומיים ל-30 דקות.
- חוסר באנליטיקה — ללא בונה, קשה לעקוב אחר המרות. אנו מטמיעים דשבורד עם התפלגות תשובות, שיעורי המרה וסשנים נטושים. לקוחות מדווחים על עלייה של 25% בשיעורי השלמת טפסים לאחר יישום אנליטיקה.
סוגי שדות נתמכים
| סוג | תיאור |
|---|---|
{ "id": "uuid-v4", "title": "Заявка на обратный звонок", "description": "Мы перезвоним в течение 30 минут", "settings": { "submit_label": "Отправить заявку", "success_message": "Спасибо! Мы свяжемся с вами.", "redirect_url": null, "notify_emails": ["[email protected]"], "allow_multiple_submissions": false }, "fields": [ { "id": "field_1", "type": "text", "label": "Имя", "placeholder": "Введите ваше имя", "required": true, "validation": { "min_length": 2, "max_length": 100 } }, { "id": "field_2", "type": "phone", "label": "Телефон", "required": true, "validation": { "pattern": "^\\+?[\\d\\s\\-\\(\\)]{7,20}$" } }, { "id": "field_3", "type": "select", "label": "Удобное время звонка", "required": false, "options": [ { "value": "morning", "label": "9:00 – 12:00" }, { "value": "afternoon", "label": "12:00 – 17:00" }, { "value": "evening", "label": "17:00 – 20:00" } ] }, { "id": "field_4", "type": "conditional_group", "condition": { "field": "field_3", "operator": "equals", "value": "evening" }, "fields": [ { "id": "field_4_1", "type": "checkbox", "label": "Подтверждаю, что звонок после 17:00 мне удобен", "required": true } ] } ] } |
טקסט בשורה אחת |
text |
טקסט רב-שורתי |
textarea |
אימייל עם וולידציה מובנית |
email |
טלפון עם מסכה |
phone |
מספר עם min/max/step |
number |
רשימה נפתחת |
select |
בחירה מרובה |
multiselect |
לחצני רדיו |
radio |
תיבת סימון יחידה (הסכמה) |
checkbox |
קבוצת תיבות סימון |
checkbox_group |
תאריך |
date |
טווח תאריכים |
date_range |
העלאת קבצים |
file |
דירוג כוכבים (1–5) |
rating |
סולם (NPS, 0–10) |
scale |
כותרת (לא שדה קלט) |
heading |
בלוק טקסט |
paragraph |
מפריד |
divider |
קבוצה עם תנאי תצוגה |
באיזו ספרייה לבחור ל-Drag-and-Drop?
ליישום drag-and-drop בעורך, אנו משתמשים בספריית @dnd-kit. היא מודרנית יותר מ-react-beautiful-dnd: היא תומכת בתרחישים מורכבים יותר — למשל, גרירה בין קונטיינרים, מיון עם אנימציה ותמיכה במסכי מגע. היא גם קלה ומהירה יותר.
import { DndContext, closestCenter, DragEndEvent } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable';
function FormBuilder({ schema, onChange }: BuilderProps) {
const [fields, setFields] = useState(schema.fields);
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
if (active.id !== over?.id) {
setFields((items) => {
const oldIndex = items.findIndex((i) => i.id === active.id);
const newIndex = items.findIndex((i) => i.id === over!.id);
const reordered = arrayMove(items, oldIndex, newIndex);
onChange({ ...schema, fields: reordered });
return reordered;
});
}
}
return (
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={fields.map(f => f.id)} strategy={verticalListSortingStrategy}>
{fields.map(field => (
<SortableFieldCard key={field.id} field={field} onEdit={(updated) => updateField(field.id, updated)} onDelete={() => removeField(field.id)} />
))}
</SortableContext>
</DndContext>
);
} רנדור וולידציה
הרנדרר עובד עם אותה סכמת JSON. לוולידציה, אנו משתמשים ב-React Hook Form — ספרייה שממזערת רנדורים חוזרים ומפשטת את כללי הוולידציה.
import { useForm } from 'react-hook-form';
function FormRenderer({ schema, onSubmit }: RendererProps) {
const { register, handleSubmit, watch, formState: { errors } } = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
{schema.fields.map(field => (
<FormField
key={field.id}
field={field}
register={register}
errors={errors}
watch={watch}
/>
))}
<button type="submit">{schema.settings.submit_label}</button>
</form>
);
}
function FormField({ field, register, errors, watch }) {
// Условная логика: показывать поле только если условие выполнено
if (field.condition) {
const watchValue = watch(field.condition.field);
const conditionMet = evaluateCondition(watchValue, field.condition);
if (!conditionMet) return null;
}
const rules = buildValidationRules(field);
switch (field.type) {
case 'text':
case 'email':
case 'phone':
return (
<div>
<label>{field.label}{field.required && ' *'}</label>
<input {...register(field.id, rules)} placeholder={field.placeholder} />
{errors[field.id] && <span>{errors[field.id].message}</span>}
</div>
);
case 'select':
return (
<div>
<label>{field.label}</label>
<select {...register(field.id, rules)}>
<option value="">Выберите...</option>
{field.options.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
</div>
);
// ... другие типы
}
} איך לאחסן נתונים ולנתח תשובות?
אחסון סכמות ותשובות מאורגן על PostgreSQL. סכמת הטופס היא JSONB, והתשובות הן גם JSONB עם אינדקס GIN לחיפוש מהיר.
CREATE TABLE forms (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
slug VARCHAR(100) UNIQUE,
schema JSONB NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_by INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE form_submissions (
id BIGSERIAL PRIMARY KEY,
form_id UUID REFERENCES forms(id),
data JSONB NOT NULL,
metadata JSONB DEFAULT '{}',
submitted_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_submissions_form ON form_submissions (form_id, submitted_at DESC);
CREATE INDEX idx_submissions_data ON form_submissions USING gin(data);
-- Аналитика: распределение ответов для поля
SELECT data->>'field_3' AS answer,
COUNT(*) AS count,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 1) AS percent
FROM form_submissions
WHERE form_id = $1 AND data ? 'field_3'
GROUP BY data->>'field_3'
ORDER BY count DESC;עיבוד שליחות נעשה דרך נקודת API אחת:
// POST /api/forms/{slug}/submit
async function submitForm(req: Request, res: Response) {
const form = await getFormBySlug(req.params.slug);
if (!form || !form.is_active) return res.status(404).json({ error: 'Form not found' });
const schema = form.schema;
const errors = validateSubmission(schema.fields, req.body);
if (Object.keys(errors).length) {
return res.status(422).json({ errors });
}
const submission = await saveSubmission(form.id, req.body, {
ip: req.ip,
user_agent: req.headers['user-agent'],
referer: req.headers.referer,
});
// Уведомления
if (schema.settings.notify_emails?.length) {
await sendNotificationEmail(form, submission);
}
if (schema.settings.webhook_url) {
await triggerWebhook(schema.settings.webhook_url, submission);
}
return res.json({
success: true,
message: schema.settings.success_message,
redirect: schema.settings.redirect_url,
});
} טעויות יישום נפוצות
- שאילתות N+1 בטעינת רשימת הטפסים — אנו פותרים זאת עם טעינה מוקדמת או קאשינג.
- בעיות הידרציה בצד השרת — טפסים עם תנאים עלולים להופיע שגוי עם SSR. אנו משתמשים ב-Suspense וטעינה הדרגתית.
- מורכבות בוולידציה של שדות מותאמים אישית — סכמת JSON עם כללים מפשטת את התחזוקה.
- חוסר הגנה מפני שליחות כפולות — אנו מיישמים טוקני CSRF ומפתחות ייחודיים.
תהליך עבודה
- אנליזה — לימוד דרישות, סוגי טפסים, אינטגרציות.
- עיצוב — פיתוח סכמת JSON ואב-טיפוס ממשק.
- יישום — כתיבת Builder, Renderer, Backend, חיבור אנליטיקה.
- בדיקות — אימות נכונות הרנדור, הוולידציה והשליחות.
- פריסה — פריסה לשרת, הגדרת ניטור.
לוח זמנים ליישום
| גרסה | זמן | עלות משוערת |
|---|---|---|
| בסיסית (טקסט, אימייל, select, checkbox, ללא לוגיקה מותנית) | 10–12 ימי עבודה | החל מ-$3,000 |
| מלאה (כל הסוגים, לוגיקה מותנית, קבצים, אנליטיקה, webhook, iframe) | 16–22 ימי עבודה | החל מ-$6,000 |
מה כלול
- רכיב עורך React מוכן עם פלטת שדות
- API לאחסון סכמות ותשובות
- דף אנליטיקה עם גרפים וייצוא
- אינטגרציה עם אימייל (התראות) ו-webhooks
- תיעוד API והתקנה
- הדרכת מנהל (1–2 שעות)
- אחריות ל-12 חודשים על הפתרון שפותח
עם ניסיון של למעלה מ-5 שנים ו-50+ פרויקטים שהושלמו, חידדנו את פתרון בונה הטפסים שלנו כדי לספק יעילות מרבית. בניגוד לפתרון מותאם אישית שבו כל שינוי דורש 4 שעות עבודת מפתח, הבונה שלנו מאפשר לשנות טופס ב-30 דקות דרך הממשק — חוסך עד 95% מהזמן ומפחית עלויות ב-87% (חיסכון של $3,000+ בשנה בממוצע). נבחן את הפרויקט שלך בשעה אחת: צור קשר כדי לדון בפרטים ולקבל הצעה מסחרית. הזמינו פיתוח בונה טפסים מפתח-בשלות היום.







