diff --git a/src/admin/components/formBuilder/FieldPalette.css b/src/admin/components/formBuilder/FieldPalette.css
new file mode 100644
index 000000000..defb23427
--- /dev/null
+++ b/src/admin/components/formBuilder/FieldPalette.css
@@ -0,0 +1,69 @@
+.formipay-field-palette {
+ padding: 16px;
+ background: #fff;
+ border-right: 1px solid #e0e0e0;
+ overflow-y: auto;
+ max-height: calc(100vh - 100px);
+}
+
+.formipay-field-palette h3 {
+ margin: 0 0 16px;
+ font-size: 14px;
+ font-weight: 600;
+ color: #1e1e1e;
+}
+
+.formipay-palette-category {
+ margin-bottom: 20px;
+}
+
+.formipay-palette-category h4 {
+ margin: 0 0 8px;
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ color: #646970;
+}
+
+.formipay-palette-items {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 8px;
+}
+
+.formipay-palette-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 12px;
+ background: #f0f0f1;
+ border: 1px solid #dcdcde;
+ border-radius: 4px;
+ cursor: grab;
+ transition: all 0.2s;
+ user-select: none;
+}
+
+.formipay-palette-item:hover {
+ background: #fff;
+ border-color: #2271b1;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.formipay-palette-item:active {
+ cursor: grabbing;
+}
+
+.formipay-palette-item svg {
+ width: 18px;
+ height: 18px;
+ fill: #1e1e1e;
+}
+
+.formipay-palette-item span {
+ font-size: 12px;
+ color: #1e1e1e;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
diff --git a/src/admin/components/formBuilder/FieldPalette.js b/src/admin/components/formBuilder/FieldPalette.js
new file mode 100644
index 000000000..c8fad8f4a
--- /dev/null
+++ b/src/admin/components/formBuilder/FieldPalette.js
@@ -0,0 +1,50 @@
+/**
+ * Field Palette - Drag-and-drop source for form fields
+ */
+
+import { __ } from '@wordpress/i18n';
+import { Icon } from '@wordpress/icons';
+import { FIELD_CATEGORIES, getFieldTypesByCategory } from '../../config/fieldTypes';
+import './FieldPalette.css';
+
+export default function FieldPalette({ onDragStart }) {
+ const fieldTypesByCategory = getFieldTypesByCategory();
+ const categories = Object.values(FIELD_CATEGORIES).sort((a, b) => a.order - b.order);
+
+ const handleDragStart = (event, fieldType) => {
+ event.dataTransfer.effectAllowed = 'copy';
+ event.dataTransfer.setData('formipay-field-type', fieldType);
+ onDragStart?.(fieldType);
+ };
+
+ return (
+
+
{ __('Add Field', 'formipay') }
+
+ {categories.map((category) => {
+ const fields = fieldTypesByCategory[category.label] || [];
+ if (fields.length === 0) return null;
+
+ return (
+
+
{ category.label }
+
+ {fields.map((field) => (
+
handleDragStart(e, field.type)}
+ title={field.label}
+ >
+
+ { field.label }
+
+ ))}
+
+
+ );
+ })}
+
+ );
+}
diff --git a/src/admin/components/formBuilder/FieldSettingsPanel.css b/src/admin/components/formBuilder/FieldSettingsPanel.css
new file mode 100644
index 000000000..5be79be93
--- /dev/null
+++ b/src/admin/components/formBuilder/FieldSettingsPanel.css
@@ -0,0 +1,48 @@
+.formipay-field-settings-panel {
+ width: 320px;
+ background: #fff;
+ border-left: 1px solid #e0e0e0;
+ display: flex;
+ flex-direction: column;
+ max-height: calc(100vh - 100px);
+}
+
+.formipay-settings-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px;
+ border-bottom: 1px solid #e0e0e0;
+}
+
+.formipay-settings-header h3 {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.formipay-settings-header .field-type-badge {
+ padding: 2px 6px;
+ font-size: 10px;
+ font-weight: 600;
+ text-transform: uppercase;
+ background: #e0e0e0;
+ border-radius: 2px;
+}
+
+.formipay-settings-content {
+ flex: 1;
+ padding: 16px;
+ overflow-y: auto;
+}
+
+.formipay-settings-content .components-base-control {
+ margin-bottom: 16px;
+}
+
+.formipay-settings-content .components-base-control:last-child {
+ margin-bottom: 0;
+}
diff --git a/src/admin/components/formBuilder/FieldSettingsPanel.js b/src/admin/components/formBuilder/FieldSettingsPanel.js
new file mode 100644
index 000000000..85b5b35ef
--- /dev/null
+++ b/src/admin/components/formBuilder/FieldSettingsPanel.js
@@ -0,0 +1,129 @@
+/**
+ * Field Settings Panel - Edit field properties
+ */
+
+import { __ } from '@wordpress/i18n';
+import { TextControl, CheckboxControl, SelectControl, TextareaControl } from '@wordpress/components';
+import { FIELD_TYPES } from '../../config/fieldTypes';
+import FormFieldOptions from './FormFieldOptions';
+import './FieldSettingsPanel.css';
+
+export default function FieldSettingsPanel({
+ field,
+ open,
+ onClose,
+ onUpdate,
+}) {
+ if (!open || !field) {
+ return null;
+ }
+
+ const fieldConfig = FIELD_TYPES[field.field_type] || FIELD_TYPES.text;
+ const hasOptions = ['select', 'checkbox', 'radio'].includes(field.field_type);
+ const hasPlaceholder = [
+ 'text', 'url', 'email', 'tel', 'number', 'date', 'datetime', 'color',
+ 'select', 'checkbox', 'radio', 'hidden', 'textarea', 'country_list'
+ ].includes(field.field_type);
+ const hasDefaultValue = [
+ 'text', 'url', 'email', 'tel', 'number', 'date', 'datetime', 'color',
+ 'select', 'checkbox', 'radio', 'hidden', 'textarea'
+ ].includes(field.field_type);
+ const hasDescription = !['hidden'].includes(field.field_type);
+ const isRequired = !['divider', 'page_break', 'hidden'].includes(field.field_type);
+ const hasGridColumns = ['radio', 'checkbox'].includes(field.field_type);
+
+ const handleChange = (key, value) => {
+ onUpdate?.({ ...field, [key]: value });
+ };
+
+ return (
+
+
+
+ { fieldConfig.label }
+ { __('Field Settings', 'formipay') }
+
+
+
+
+
+ handleChange('label', value)}
+ help={ __('The display label for this field', 'formipay') }
+ />
+
+ handleChange('field_id', value)}
+ help={ __('Unique identifier for this field (used in form data)', 'formipay') }
+ />
+
+ { hasPlaceholder && (
+ handleChange('placeholder', value)}
+ />
+ ) }
+
+ { hasDefaultValue && (
+ handleChange('default_value', value)}
+ />
+ ) }
+
+ { hasDescription && (
+ handleChange('description', value)}
+ help={ __('Optional help text displayed below the field', 'formipay') }
+ rows={3}
+ />
+ ) }
+
+ { hasOptions && (
+ handleChange('field_options', value)}
+ fieldType={field.field_type}
+ />
+ ) }
+
+ { hasGridColumns && (
+ handleChange('option_grid_columns', parseInt(value))}
+ />
+ ) }
+
+ { isRequired && (
+ handleChange('is_required', value)}
+ help={ __('User must fill this field before submitting', 'formipay') }
+ />
+ ) }
+
+
+ );
+}
diff --git a/src/admin/components/formBuilder/FormBuilder.css b/src/admin/components/formBuilder/FormBuilder.css
new file mode 100644
index 000000000..66e94b785
--- /dev/null
+++ b/src/admin/components/formBuilder/FormBuilder.css
@@ -0,0 +1,31 @@
+.formipay-form-builder {
+ display: flex;
+ flex-direction: column;
+ height: calc(100vh - 32px);
+}
+
+.formipay-builder-toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 20px;
+ background: #fff;
+ border-bottom: 1px solid #e0e0e0;
+}
+
+.formipay-builder-toolbar h2 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.formipay-builder-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.formipay-builder-content {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+}
diff --git a/src/admin/components/formBuilder/FormBuilder.js b/src/admin/components/formBuilder/FormBuilder.js
new file mode 100644
index 000000000..2394c9ab5
--- /dev/null
+++ b/src/admin/components/formBuilder/FormBuilder.js
@@ -0,0 +1,114 @@
+/**
+ * Form Builder - Main container component
+ */
+
+import { __ } from '@wordpress/i18n';
+import { useState, useCallback } from '@wordpress/element';
+import FormCanvas from './FormCanvas';
+import FieldPalette from './FieldPalette';
+import FieldSettingsPanel from './FieldSettingsPanel';
+import FormPreview from './FormPreview';
+import './FormBuilder.css';
+
+export default function FormBuilder({ formId, initialData = {} }) {
+ const [fields, setFields] = useState(initialData.fields || []);
+ const [selectedFieldId, setSelectedFieldId] = useState(null);
+
+ const selectedField = fields.find(f => f.field_id === selectedFieldId) || null;
+
+ const handleDrop = useCallback((newField) => {
+ setFields([...fields, newField]);
+ setSelectedFieldId(newField.field_id);
+ }, [fields]);
+
+ const handleSelectField = useCallback((fieldId) => {
+ setSelectedFieldId(fieldId);
+ }, []);
+
+ const handleUpdateField = useCallback((fieldId, updates) => {
+ setFields(fields.map(f => f.field_id === fieldId ? { ...f, ...updates } : f));
+ }, [fields]);
+
+ const handleMoveField = useCallback((newFields) => {
+ setFields(newFields);
+ }, []);
+
+ const handleDeleteField = useCallback((fieldId) => {
+ const newFields = fields.filter(f => f.field_id !== fieldId);
+ setFields(newFields);
+ if (selectedFieldId === fieldId) {
+ setSelectedFieldId(null);
+ }
+ }, [fields, selectedFieldId]);
+
+ const handleSave = useCallback(() => {
+ // Save form fields via AJAX
+ const formData = new FormData();
+ formData.append('action', 'formipay_save_form_fields');
+ formData.append('post_id', formId);
+ formData.append('fields', JSON.stringify(fields));
+ formData.append('_wpnonce', window.formipayAdmin?.nonce || '');
+
+ fetch(window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php', {
+ method: 'POST',
+ credentials: 'same-origin',
+ body: formData,
+ })
+ .then(response => response.json())
+ .then(result => {
+ if (result.success) {
+ // Show success message
+ console.log('Form saved successfully');
+ } else {
+ console.error('Failed to save form:', result.message);
+ }
+ })
+ .catch(error => {
+ console.error('Save error:', error);
+ });
+ }, [fields, formId]);
+
+ return (
+
+
+
{ __('Form Builder', 'formipay') }
+
+
+
+
+
+
+
+ {}} />
+
+
+ setSelectedFieldId(null)}
+ onUpdate={handleUpdateField}
+ />
+
+
+ );
+}
diff --git a/src/admin/components/formBuilder/FormCanvas.css b/src/admin/components/formBuilder/FormCanvas.css
new file mode 100644
index 000000000..77777a9f9
--- /dev/null
+++ b/src/admin/components/formBuilder/FormCanvas.css
@@ -0,0 +1,62 @@
+.formipay-form-canvas {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ background: #f6f7f7;
+ min-width: 0;
+}
+
+.formipay-canvas-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ background: #fff;
+ border-bottom: 1px solid #e0e0e0;
+}
+
+.formipay-canvas-header h3 {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 600;
+ color: #1e1e1e;
+}
+
+.field-count {
+ font-size: 12px;
+ color: #646970;
+}
+
+.formipay-canvas-area {
+ flex: 1;
+ padding: 20px;
+ overflow-y: auto;
+}
+
+.formipay-canvas-area.is-empty {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.formipay-empty-state {
+ text-align: center;
+ color: #646970;
+}
+
+.formipay-empty-state svg {
+ display: block;
+ margin: 0 auto 16px;
+ fill: #c3c4c7;
+}
+
+.formipay-empty-state p {
+ margin: 0;
+ font-size: 14px;
+}
+
+.formipay-fields-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
diff --git a/src/admin/components/formBuilder/FormCanvas.js b/src/admin/components/formBuilder/FormCanvas.js
new file mode 100644
index 000000000..3d4612d0f
--- /dev/null
+++ b/src/admin/components/formBuilder/FormCanvas.js
@@ -0,0 +1,103 @@
+/**
+ * Form Canvas - Drop target for form fields
+ */
+
+import { __ } from '@wordpress/i18n';
+import { Icon, plus } from '@wordpress/icons';
+import { DEFAULT_FIELD_CONFIG, generateFieldId } from '../../config/fieldTypes';
+import FormField from './FormField';
+import './FormCanvas.css';
+
+export default function FormCanvas({
+ fields = [],
+ selectedFieldId,
+ onDrop,
+ onSelectField,
+ onUpdateField,
+ onMoveField,
+ onDeleteField,
+}) {
+ const handleDragOver = (event) => {
+ event.preventDefault();
+ event.dataTransfer.dropEffect = 'copy';
+ };
+
+ const handleDrop = (event) => {
+ event.preventDefault();
+ const fieldType = event.dataTransfer.getData('formipay-field-type');
+ if (!fieldType) return;
+
+ const newField = {
+ ...DEFAULT_FIELD_CONFIG,
+ field_type: fieldType,
+ field_id: generateFieldId(fieldType.replace('_', ' ')),
+ label: fieldType.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()),
+ };
+
+ onDrop?.(newField);
+ };
+
+ const handleReorder = (dragIndex, dropIndex) => {
+ const newFields = [...fields];
+ const [draggedField] = newFields.splice(dragIndex, 1);
+ newFields.splice(dropIndex, 0, draggedField);
+ onMoveField?.(newFields);
+ };
+
+ return (
+
+
+
{ __('Form Fields', 'formipay') }
+
+ { fields.length } { __('fields', 'formipay') }
+
+
+
+
+ {fields.length === 0 ? (
+
+
+
+ { __('Drag fields from the palette to build your form', 'formipay') }
+
+
+ ) : (
+
+ {fields.map((field, index) => (
+ onSelectField?.(field.field_id)}
+ onUpdate={(updates) => onUpdateField?.(field.field_id, updates)}
+ onMoveUp={index > 0 ? () => handleReorder(index, index - 1) : null}
+ onMoveDown={index < fields.length - 1 ? () => handleReorder(index, index + 1) : null}
+ onDelete={() => onDeleteField?.(field.field_id)}
+ onDragStart={(e) => {
+ e.dataTransfer.effectAllowed = 'move';
+ e.dataTransfer.setData('formipay-field-index', index.toString());
+ }}
+ onDragOver={(e) => {
+ e.preventDefault();
+ e.dataTransfer.dropEffect = 'move';
+ }}
+ onDrop={(e) => {
+ e.preventDefault();
+ const fromIndex = parseInt(e.dataTransfer.getData('formipay-field-index'));
+ if (!isNaN(fromIndex) && fromIndex !== index) {
+ handleReorder(fromIndex, index);
+ }
+ }}
+ />
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/admin/components/formBuilder/FormField.css b/src/admin/components/formBuilder/FormField.css
new file mode 100644
index 000000000..6bf3b26c0
--- /dev/null
+++ b/src/admin/components/formBuilder/FormField.css
@@ -0,0 +1,148 @@
+.formipay-field-item {
+ background: #fff;
+ border: 1px solid #dcdcde;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.2s;
+ user-select: none;
+}
+
+.formipay-field-item:hover {
+ border-color: #a7aaad;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+}
+
+.formipay-field-item.is-selected {
+ border-color: #2271b1;
+ box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.2);
+}
+
+.formipay-field-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 14px;
+ background: #f6f7f7;
+ border-bottom: 1px solid #e0e0e0;
+ border-radius: 4px 4px 0 0;
+}
+
+.formipay-field-info {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.field-type-badge {
+ display: inline-block;
+ padding: 2px 8px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ background: #e0e0e0;
+ color: #1e1e1e;
+ border-radius: 2px;
+}
+
+.field-id {
+ font-size: 11px;
+ color: #646970;
+ font-family: monospace;
+}
+
+.formipay-field-actions {
+ display: flex;
+ gap: 4px;
+}
+
+.formipay-field-actions .button-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ padding: 0;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ border-radius: 2px;
+ transition: background 0.2s;
+}
+
+.formipay-field-actions .button-icon:hover:not(:disabled) {
+ background: #e0e0e0;
+}
+
+.formipay-field-actions .button-icon:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.formipay-field-actions .button-icon svg {
+ fill: #1e1e1e;
+}
+
+.formipay-field-actions .button-danger:hover svg {
+ fill: #d63638;
+}
+
+.formipay-field-content {
+ padding: 14px;
+}
+
+.field-label-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 8px;
+}
+
+.field-label-row strong {
+ font-size: 14px;
+ color: #1e1e1e;
+}
+
+.field-label-row em {
+ color: #a7aaad;
+ font-style: italic;
+}
+
+.required-badge {
+ display: inline-block;
+ padding: 2px 6px;
+ font-size: 10px;
+ font-weight: 600;
+ color: #d63638;
+ background: #f6f7f7;
+ border-radius: 2px;
+}
+
+.field-description {
+ margin: 0 0 8px;
+ font-size: 12px;
+ color: #646970;
+ line-height: 1.4;
+}
+
+.field-options-preview {
+ padding: 8px;
+ margin-bottom: 8px;
+ background: #f6f7f7;
+ border-radius: 2px;
+}
+
+.field-options-preview small {
+ font-size: 11px;
+ color: #646970;
+}
+
+.field-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+}
+
+.field-meta .meta-item {
+ font-size: 11px;
+ color: #646970;
+}
diff --git a/src/admin/components/formBuilder/FormField.js b/src/admin/components/formBuilder/FormField.js
new file mode 100644
index 000000000..aa675bdaf
--- /dev/null
+++ b/src/admin/components/formBuilder/FormField.js
@@ -0,0 +1,113 @@
+/**
+ * Form Field - Individual field component in canvas
+ */
+
+import { __ } from '@wordpress/i18n';
+import { Icon, chevronUp, chevronDown, trash } from '@wordpress/icons';
+import { FIELD_TYPES } from '../../config/fieldTypes';
+import './FormField.css';
+
+export default function FormField({
+ field,
+ index,
+ isSelected,
+ onSelect,
+ onUpdate,
+ onMoveUp,
+ onMoveDown,
+ onDelete,
+ onDragStart,
+ onDragOver,
+ onDrop,
+}) {
+ const fieldConfig = FIELD_TYPES[field.field_type] || FIELD_TYPES.text;
+ const hasOptions = ['select', 'checkbox', 'radio'].includes(field.field_type);
+
+ return (
+
+
+
+
+ { fieldConfig.label }
+
+
+ #{field.field_id || index}
+
+
+
+
+
+
+
+
+
+
+
+ { field.label || Untitled }
+ { field.is_required && (
+
+ { __('Required', 'formipay') }
+
+ )}
+
+
+ { field.description && (
+
+ { field.description }
+
+ )}
+
+ { hasOptions && field.field_options && field.field_options.length > 0 && (
+
+
+ { field.field_options.length } { __('options', 'formipay') }
+
+
+ )}
+
+
+ { field.placeholder && (
+
+ Placeholder: "{ field.placeholder }"
+
+ )}
+ { field.default_value && (
+
+ Default: "{ field.default_value }"
+
+ )}
+
+
+
+ );
+}
diff --git a/src/admin/components/formBuilder/FormFieldOptions.css b/src/admin/components/formBuilder/FormFieldOptions.css
new file mode 100644
index 000000000..dbce102f4
--- /dev/null
+++ b/src/admin/components/formBuilder/FormFieldOptions.css
@@ -0,0 +1,65 @@
+.formipay-field-options {
+ margin-top: 16px;
+ padding: 16px;
+ background: #f6f7f7;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+}
+
+.formipay-options-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.formipay-options-header label {
+ font-size: 13px;
+ font-weight: 600;
+ color: #1e1e1e;
+}
+
+.formipay-options-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.formipay-option-item {
+ display: flex;
+ gap: 8px;
+ align-items: flex-start;
+}
+
+.formipay-option-fields {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.formipay-option-fields .components-base-control {
+ margin: 0;
+}
+
+.formipay-option-fields .components-base-control__label {
+ font-size: 11px;
+}
+
+.formipay-option-actions {
+ padding-top: 20px;
+}
+
+.formipay-no-options {
+ padding: 20px;
+ text-align: center;
+ background: #fff;
+ border: 1px dashed #dcdcde;
+ border-radius: 2px;
+}
+
+.formipay-no-options p {
+ margin: 0;
+ font-size: 12px;
+ color: #646970;
+}
diff --git a/src/admin/components/formBuilder/FormFieldOptions.js b/src/admin/components/formBuilder/FormFieldOptions.js
new file mode 100644
index 000000000..a3f5737b5
--- /dev/null
+++ b/src/admin/components/formBuilder/FormFieldOptions.js
@@ -0,0 +1,105 @@
+/**
+ * Form Field Options - Manage select/radio/checkbox options
+ */
+
+import { __ } from '@wordpress/i18n';
+import { TextControl, Button } from '@wordpress/components';
+import { Icon as WPIcon, plus } from '@wordpress/icons';
+
+export default function FormFieldOptions({ options = [], onChange, fieldType }) {
+ const handleAddOption = () => {
+ const newOption = {
+ label: '',
+ value: '',
+ amount: '',
+ weight: '',
+ quantity: false,
+ };
+ onChange([...options, newOption]);
+ };
+
+ const handleUpdateOption = (index, updates) => {
+ const newOptions = [...options];
+ newOptions[index] = { ...newOptions[index], ...updates };
+ onChange(newOptions);
+ };
+
+ const handleDeleteOption = (index) => {
+ const newOptions = options.filter((_, i) => i !== index);
+ onChange(newOptions);
+ };
+
+ const isMultiSelect = fieldType === 'checkbox';
+
+ return (
+
+
+
+ }
+ >
+ { __('Add Option', 'formipay') }
+
+
+
+
+ {options.map((option, index) => (
+
+
+ handleUpdateOption(index, { label: value })}
+ placeholder={__('Option label', 'formipay')}
+ />
+
+ handleUpdateOption(index, { value: value })}
+ placeholder={__('Optional custom value', 'formipay')}
+ />
+
+ handleUpdateOption(index, { amount: value })}
+ placeholder={__('0', 'formipay')}
+ step="0.01"
+ />
+
+
+
+
+
+ ))}
+
+ {options.length === 0 && (
+
+
+ { __('No options added yet. Click "Add Option" to create one.', 'formipay') }
+
+
+ )}
+
+
+ );
+}
diff --git a/src/admin/components/formBuilder/FormFieldPreview.css b/src/admin/components/formBuilder/FormFieldPreview.css
new file mode 100644
index 000000000..cb02f48df
--- /dev/null
+++ b/src/admin/components/formBuilder/FormFieldPreview.css
@@ -0,0 +1,87 @@
+.formipay-preview-form {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.formipay-field-preview {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.formipay-field-label {
+ display: block;
+ font-size: 13px;
+ font-weight: 500;
+ color: #1e1e1e;
+}
+
+.formipay-field-label .required {
+ color: #d63638;
+ margin-left: 2px;
+}
+
+.formipay-input,
+.formipay-textarea,
+.formipay-select {
+ width: 100%;
+ padding: 8px 12px;
+ font-size: 13px;
+ color: #1e1e1e;
+ background: #fff;
+ border: 1px solid #8c8f94;
+ border-radius: 2px;
+}
+
+.formipay-input:focus,
+.formipay-textarea:focus,
+.formipay-select:focus {
+ outline: none;
+ border-color: #2271b1;
+ box-shadow: 0 0 0 1px #2271b1;
+}
+
+.formipay-textarea {
+ resize: vertical;
+ min-height: 80px;
+}
+
+.formipay-radio-group,
+.formipay-checkbox-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.formipay-radio-label,
+.formipay-checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 13px;
+ color: #1e1e1e;
+ cursor: not-allowed;
+}
+
+.formipay-field-description {
+ margin: 0;
+ font-size: 12px;
+ color: #646970;
+}
+
+.formipay-divider {
+ border: none;
+ border-top: 1px solid #c3c4c7;
+ margin: 8px 0;
+}
+
+.formipay-page-break {
+ padding: 12px;
+ text-align: center;
+ background: #f6f7f7;
+ border: 1px dashed #c3c4c7;
+ border-radius: 2px;
+ font-size: 12px;
+ color: #646970;
+}
diff --git a/src/admin/components/formBuilder/FormFieldPreview.js b/src/admin/components/formBuilder/FormFieldPreview.js
new file mode 100644
index 000000000..3fe32a184
--- /dev/null
+++ b/src/admin/components/formBuilder/FormFieldPreview.js
@@ -0,0 +1,142 @@
+/**
+ * Form Field Preview - Preview individual form fields
+ */
+
+import { __ } from '@wordpress/i18n';
+import './FormFieldPreview.css';
+
+export default function FormFieldPreview({ field }) {
+ const fieldType = field.field_type || 'text';
+ const label = field.label || '';
+ const placeholder = field.placeholder || '';
+ const defaultValue = field.default_value || '';
+ const description = field.description || '';
+ const isRequired = field.is_required || false;
+ const fieldId = field.field_id || `field_${Math.random().toString(36).slice(2, 11)}`;
+
+ const renderFieldInput = () => {
+ const commonProps = {
+ id: fieldId,
+ name: fieldId,
+ placeholder,
+ defaultValue,
+ required: isRequired,
+ disabled: true,
+ };
+
+ switch (fieldType) {
+ case 'text':
+ case 'url':
+ case 'email':
+ case 'tel':
+ case 'number':
+ case 'date':
+ case 'datetime':
+ case 'color':
+ return (
+
+ );
+
+ case 'textarea':
+ return (
+
+ );
+
+ case 'select':
+ return (
+
+ );
+
+ case 'radio':
+ return (
+
+ {field.field_options?.map((option, index) => (
+
+ ))}
+
+ );
+
+ case 'checkbox':
+ return (
+
+ {field.field_options?.map((option, index) => (
+
+ ))}
+
+ );
+
+ case 'divider':
+ return
;
+
+ case 'page_break':
+ return { __('Page Break', 'formipay') }
;
+
+ case 'country_list':
+ return (
+
+ );
+
+ default:
+ return ;
+ }
+ };
+
+ const isLayout = ['divider', 'page_break'].includes(fieldType);
+
+ return (
+
+ {!isLayout && label && (
+
+ )}
+
+ { renderFieldInput() }
+
+ { description && !isLayout && (
+
+ { description }
+
+ )}
+
+ );
+}
diff --git a/src/admin/components/formBuilder/FormPreview.css b/src/admin/components/formBuilder/FormPreview.css
new file mode 100644
index 000000000..71993bcbf
--- /dev/null
+++ b/src/admin/components/formBuilder/FormPreview.css
@@ -0,0 +1,39 @@
+.formipay-form-preview {
+ display: flex;
+ flex-direction: column;
+ background: #fff;
+ border-right: 1px solid #e0e0e0;
+ width: 320px;
+}
+
+.formipay-preview-header {
+ padding: 12px 16px;
+ background: #f6f7f7;
+ border-bottom: 1px solid #e0e0e0;
+}
+
+.formipay-preview-header h4 {
+ margin: 0;
+ font-size: 13px;
+ font-weight: 600;
+ color: #1e1e1e;
+}
+
+.formipay-preview-content {
+ flex: 1;
+ padding: 16px;
+ overflow-y: auto;
+}
+
+.formipay-preview-content.is-empty {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 200px;
+}
+
+.formipay-preview-content.is-empty p {
+ color: #646970;
+ font-size: 13px;
+ margin: 0;
+}
diff --git a/src/admin/components/formBuilder/FormPreview.js b/src/admin/components/formBuilder/FormPreview.js
new file mode 100644
index 000000000..b118356e4
--- /dev/null
+++ b/src/admin/components/formBuilder/FormPreview.js
@@ -0,0 +1,40 @@
+/**
+ * Form Preview - Live preview of form rendering
+ */
+
+import { __ } from '@wordpress/i18n';
+import FormFieldPreview from './FormFieldPreview';
+import './FormPreview.css';
+
+export default function FormPreview({ fields }) {
+ if (!fields || fields.length === 0) {
+ return (
+
+
+
{ __('Live Preview', 'formipay') }
+
+
+
{ __('Add fields to see the preview', 'formipay') }
+
+
+ );
+ }
+
+ return (
+
+
+
{ __('Live Preview', 'formipay') }
+
+
+
+
+
+ );
+}
diff --git a/src/admin/config/fieldTypes.js b/src/admin/config/fieldTypes.js
new file mode 100644
index 000000000..7236a7d8a
--- /dev/null
+++ b/src/admin/config/fieldTypes.js
@@ -0,0 +1,67 @@
+/**
+ * Form Field Type Definitions
+ */
+
+export const FIELD_TYPES = {
+ text: { label: 'Text', icon: 'text', category: 'input' },
+ url: { label: 'URL', icon: 'link', category: 'input' },
+ email: { label: 'Email', icon: 'email', category: 'input' },
+ tel: { label: 'Telephone', icon: 'phone', category: 'input' },
+ number: { label: 'Number', icon: 'number', category: 'input' },
+ date: { label: 'Date', icon: 'calendar', category: 'input' },
+ datetime: { label: 'Date & Time', icon: 'calendar-alt', category: 'input' },
+ color: { label: 'Color', icon: 'art', category: 'input' },
+ select: { label: 'Select Dropdown', icon: 'list-view', category: 'choice' },
+ checkbox: { label: 'Checkbox', icon: 'checkbox', category: 'choice' },
+ radio: { label: 'Radio', icon: 'radio', category: 'choice' },
+ hidden: { label: 'Hidden', icon: 'hidden', category: 'advanced' },
+ textarea: { label: 'Textarea', icon: 'document', category: 'input' },
+ divider: { label: 'Divider', icon: 'minus', category: 'layout' },
+ page_break: { label: 'Page Break', icon: 'page-break', category: 'layout' },
+ country_list: { label: 'Preset: Country List', icon: 'globe', category: 'preset' },
+};
+
+export const FIELD_CATEGORIES = {
+ input: { label: 'Input Fields', order: 1 },
+ choice: { label: 'Choice Fields', order: 2 },
+ layout: { label: 'Layout', order: 3 },
+ preset: { label: 'Presets', order: 4 },
+ advanced: { label: 'Advanced', order: 5 },
+};
+
+export const DEFAULT_FIELD_CONFIG = {
+ field_type: 'text',
+ label: '',
+ field_id: '',
+ placeholder: '',
+ default_value: '',
+ description: '',
+ is_required: false,
+ field_options: [],
+ option_grid_columns: 1,
+};
+
+/**
+ * Get field types grouped by category
+ */
+export function getFieldTypesByCategory() {
+ const grouped = {};
+
+ Object.entries(FIELD_TYPES).forEach(([type, config]) => {
+ if (!grouped[config.category]) {
+ grouped[config.category] = [];
+ }
+ grouped[config.category].push({ type, ...config });
+ });
+
+ return grouped;
+}
+
+/**
+ * Generate unique field ID
+ */
+export function generateFieldId(label) {
+ const base = label.toLowerCase().replace(/[^a-z0-9]/g, '_');
+ const timestamp = Date.now().toString(36);
+ return `${base}_${timestamp}`;
+}
diff --git a/src/admin/pages/Forms.js b/src/admin/pages/Forms.js
index 1928e7f72..157093882 100644
--- a/src/admin/pages/Forms.js
+++ b/src/admin/pages/Forms.js
@@ -1,14 +1,32 @@
/**
- * Forms Page - Placeholder
+ * Forms Page - List view and Form Builder
*/
import { __ } from '@wordpress/i18n';
+import { useState } from '@wordpress/element';
+import FormBuilder from '../components/formBuilder/FormBuilder';
export default function FormsPage({ initialData }) {
+ const [isBuilder, setIsBuilder] = useState(false);
+ const [selectedFormId, setSelectedFormId] = useState(null);
+
+ if (isBuilder) {
+ return (
+
+
+
+ );
+ }
+
return (
-
-
{ __('Forms', 'formipay') }
-
{ __('Page content coming soon...', 'formipay') }
+
+
+
{ __('Forms', 'formipay') }
+
{ __('Forms list coming soon. Use the classic editor for now.', 'formipay') }
+
);
}