Add Form Builder Agent Documentation and Import/Export Functionality

- Introduced new documentation files: `agent-example-usage.md`, `agent-instructions-form-generator.md`, and `form-builder-json-structure.md` to provide comprehensive guidance on using the Form Builder Agent, including example usage and JSON structure.
- Implemented import and export functionality in the form management interface, allowing users to upload JSON files and download forms as JSON.
- Enhanced the `manage.vue` component with modals for importing forms, including options for file upload and pasting JSON content, along with validation feedback.
- Developed a new API endpoint for importing forms, ensuring proper validation and processing of incoming JSON data.
- Updated the form management logic to handle JSON imports and exports seamlessly, improving user experience and form management capabilities.
This commit is contained in:
Md Afiq Iskandar 2025-07-29 11:17:30 +08:00
parent f4eff35c4b
commit 1448aef0ed
6 changed files with 2662 additions and 1 deletions

799
docs/agent-example-usage.md Normal file
View File

@ -0,0 +1,799 @@
# Form Builder Agent - Example Usage
## Example 1: Employee Onboarding Form URS
### User Requirements (URS)
```
We need an employee onboarding form with the following requirements:
1. Personal Information Section:
- Full name (required)
- Email address (required, must be valid email)
- Phone number (required)
- Date of birth (required, must be 18+)
- Address (street, city, state, zip code)
2. Employment Information:
- Position/Job title (required)
- Department (dropdown: HR, IT, Finance, Marketing, Sales)
- Start date (required)
- Employment type (radio: Full-time, Part-time, Contract)
- Salary (required, numeric, min $30,000)
3. Documents Section:
- ID document upload (required, PDF/Image)
- Resume upload (required, PDF/DOC)
- Emergency contact form (name, phone, relationship)
4. Business Rules:
- If employment type is "Manager", show additional fields for team size and direct reports
- Auto-calculate years of experience from start date
- Validate email format in real-time
- Show success message when form is completed
```
### Agent Response
## Form Analysis
Based on your employee onboarding requirements, I've created a comprehensive form with:
- Personal information section with address fields
- Employment details with conditional manager fields
- Document upload section with file validation
- Emergency contact information
- Auto-calculation of experience years
- Real-time email validation
## Component Count
Total: 18 components including 4 sections with conditional logic
## Special Features
- Conditional logic shows manager-specific fields when "Manager" role is selected
- Auto-calculates years of experience from start date
- File upload validation for required documents (PDF, DOC, images)
- Real-time email format validation
- Success message on form completion
## JSON Output
```json
{
"formName": "Employee Onboarding Form",
"formDescription": "Complete employee registration and onboarding process for new hires",
"formId": null,
"components": [
{
"type": "heading",
"props": {
"name": "form_title",
"value": "Employee Onboarding Form",
"level": 1,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "paragraph",
"props": {
"name": "form_description",
"value": "Please complete all required fields to begin your onboarding process. All information will be kept confidential.",
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "heading",
"props": {
"name": "personal_section",
"value": "Personal Information",
"level": 2,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "full_name",
"label": "Full Name",
"placeholder": "Enter your full name",
"help": "Please provide your first and last name as it appears on official documents",
"validation": "required|min:2",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "email",
"props": {
"name": "email_address",
"label": "Email Address",
"placeholder": "employee@company.com",
"help": "Your company email address for communications",
"validation": "required|email",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "tel",
"props": {
"name": "phone_number",
"label": "Phone Number",
"placeholder": "(555) 123-4567",
"help": "Your primary contact number",
"validation": "required|tel",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "date",
"props": {
"name": "date_of_birth",
"label": "Date of Birth",
"help": "Must be 18 years or older",
"validation": "required",
"value": "",
"max": "2006-12-31",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "street_address",
"label": "Street Address",
"placeholder": "123 Main Street",
"help": "Your current residential address",
"validation": "required",
"value": "",
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "city",
"label": "City",
"placeholder": "City name",
"help": "City of residence",
"validation": "required",
"value": "",
"width": "33.33%",
"gridColumn": "span 4",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "state",
"label": "State",
"placeholder": "State",
"help": "State or province",
"validation": "required",
"value": "",
"width": "33.33%",
"gridColumn": "span 4",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "zip_code",
"label": "ZIP Code",
"placeholder": "12345",
"help": "Postal/ZIP code",
"validation": "required|regex:/^[0-9]{5}(-[0-9]{4})?$/",
"value": "",
"width": "33.33%",
"gridColumn": "span 4",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "heading",
"props": {
"name": "employment_section",
"value": "Employment Information",
"level": 2,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "job_title",
"label": "Position/Job Title",
"placeholder": "Software Engineer",
"help": "Your official job title",
"validation": "required",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "select",
"props": {
"name": "department",
"label": "Department",
"help": "Select your assigned department",
"validation": "required",
"value": "",
"placeholder": "Choose department",
"options": [
{ "label": "Human Resources", "value": "hr" },
{ "label": "Information Technology", "value": "it" },
{ "label": "Finance", "value": "finance" },
{ "label": "Marketing", "value": "marketing" },
{ "label": "Sales", "value": "sales" }
],
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "date",
"props": {
"name": "start_date",
"label": "Start Date",
"help": "Your official employment start date",
"validation": "required",
"value": "",
"min": "2024-01-01",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "radio",
"props": {
"name": "employment_type",
"label": "Employment Type",
"help": "Select your employment classification",
"validation": "required",
"value": "",
"options": [
{ "label": "Full-time", "value": "full_time" },
{ "label": "Part-time", "value": "part_time" },
{ "label": "Contract", "value": "contract" },
{ "label": "Manager", "value": "manager" }
],
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "number",
"props": {
"name": "salary",
"label": "Annual Salary (USD)",
"placeholder": "50000",
"help": "Your annual salary in USD",
"validation": "required|min:30000",
"value": "",
"min": 30000,
"step": 1000,
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "number",
"props": {
"name": "years_experience",
"label": "Years of Experience",
"placeholder": "0",
"help": "Calculated from start date",
"validation": "",
"value": 0,
"readonly": true,
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "number",
"props": {
"name": "team_size",
"label": "Team Size (Direct Reports)",
"placeholder": "0",
"help": "Number of direct reports if applicable",
"validation": "min:0",
"value": 0,
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": true,
"operator": "eq",
"conditions": [
{
"field": "employment_type",
"value": "manager",
"operator": "eq"
}
]
}
}
},
{
"type": "textarea",
"props": {
"name": "manager_responsibilities",
"label": "Manager Responsibilities",
"placeholder": "Describe your key responsibilities as a manager...",
"help": "Brief description of your management role",
"validation": "",
"value": "",
"rows": 3,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": true,
"operator": "eq",
"conditions": [
{
"field": "employment_type",
"value": "manager",
"operator": "eq"
}
]
}
}
},
{
"type": "heading",
"props": {
"name": "documents_section",
"value": "Required Documents",
"level": 2,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "file",
"props": {
"name": "id_document",
"label": "ID Document",
"help": "Upload a government-issued ID (passport, driver's license, etc.)",
"validation": "required",
"accept": ".pdf,.jpg,.jpeg,.png",
"multiple": false,
"maxSize": "10MB",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "file",
"props": {
"name": "resume",
"label": "Resume/CV",
"help": "Upload your current resume or CV",
"validation": "required",
"accept": ".pdf,.doc,.docx",
"multiple": false,
"maxSize": "5MB",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "heading",
"props": {
"name": "emergency_contact_section",
"value": "Emergency Contact",
"level": 2,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "emergency_contact_name",
"label": "Emergency Contact Name",
"placeholder": "Contact's full name",
"help": "Name of person to contact in case of emergency",
"validation": "required",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "tel",
"props": {
"name": "emergency_contact_phone",
"label": "Emergency Contact Phone",
"placeholder": "(555) 123-4567",
"help": "Phone number for emergency contact",
"validation": "required|tel",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "emergency_contact_relationship",
"label": "Relationship",
"placeholder": "Spouse, Parent, Friend, etc.",
"help": "Your relationship to the emergency contact",
"validation": "required",
"value": "",
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "button",
"props": {
"name": "submit_button",
"label": "Submit Onboarding Form",
"buttonType": "submit",
"variant": "primary",
"size": "lg",
"disabled": false,
"width": "25%",
"gridColumn": "span 3",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
}
],
"customScript": "// Auto-calculate years of experience from start date\nonFieldChange(['start_date'], () => {\n const startDate = new Date(getField('start_date'));\n const today = new Date();\n if (startDate && !isNaN(startDate.getTime())) {\n const years = Math.floor((today - startDate) / (1000 * 60 * 60 * 24 * 365));\n setField('years_experience', Math.max(0, years));\n }\n});\n\n// Real-time email validation\nonFieldChange(['email_address'], () => {\n const email = getField('email_address');\n if (email && !email.includes('@')) {\n showError('Please enter a valid email address');\n } else if (email && email.includes('@')) {\n showSuccess('Email format looks good!');\n }\n});\n\n// Show success message on form load\nshowInfo('Welcome to the onboarding process! Please complete all required fields.');",
"customCSS": ".form-container {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n padding: 30px;\n border-radius: 15px;\n box-shadow: 0 10px 30px rgba(0,0,0,0.2);\n}\n\n.form-field {\n margin-bottom: 20px;\n}\n\n.form-field[data-name='submit_button'] {\n margin-top: 30px;\n text-align: center;\n}",
"formEvents": {
"onLoad": true,
"onFieldChange": true,
"onSubmit": false,
"onValidation": false
},
"scriptMode": "safe"
}
```
## Usage Notes
- Form includes conditional logic for manager-specific fields (team size, responsibilities)
- File upload accepts PDF, DOC, and image formats with size limits
- Email validation runs in real-time with user feedback
- Experience calculation updates automatically when start date changes
- Form has professional styling with gradient background
- All required fields are properly validated
- Emergency contact section for safety compliance
---
## Example 2: Simple Contact Form URS
### User Requirements
```
Need a simple contact form with:
- Name field
- Email field
- Subject dropdown (General, Support, Sales, Other)
- Message textarea
- Newsletter signup checkbox
```
### Agent Response
## Form Analysis
Created a clean, simple contact form with basic fields and newsletter signup option.
## Component Count
Total: 6 components
## Special Features
- Email validation
- Newsletter signup with checkbox
- Responsive layout
## JSON Output
```json
{
"formName": "Contact Form",
"formDescription": "Simple contact form for general inquiries",
"formId": null,
"components": [
{
"type": "heading",
"props": {
"name": "form_title",
"value": "Contact Us",
"level": 2,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "name",
"label": "Full Name",
"placeholder": "Enter your name",
"help": "Please provide your full name",
"validation": "required|min:2",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "email",
"props": {
"name": "email",
"label": "Email Address",
"placeholder": "your.email@example.com",
"help": "We'll use this to respond to your inquiry",
"validation": "required|email",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "select",
"props": {
"name": "subject",
"label": "Subject",
"help": "Select the topic of your inquiry",
"validation": "required",
"value": "",
"placeholder": "Choose a subject",
"options": [
{ "label": "General Inquiry", "value": "general" },
{ "label": "Technical Support", "value": "support" },
{ "label": "Sales Question", "value": "sales" },
{ "label": "Other", "value": "other" }
],
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "textarea",
"props": {
"name": "message",
"label": "Message",
"placeholder": "Please describe your inquiry...",
"help": "Provide details about your question or request",
"validation": "required|min:10",
"value": "",
"rows": 5,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "checkbox",
"props": {
"name": "newsletter_signup",
"label": "Subscribe to Newsletter",
"help": "Receive updates about our products and services",
"validation": "",
"value": [],
"options": [
{ "label": "Yes, I want to receive newsletters", "value": "yes" }
],
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
}
],
"customScript": "",
"customCSS": "",
"formEvents": {
"onLoad": false,
"onFieldChange": false,
"onSubmit": false,
"onValidation": false
},
"scriptMode": "safe"
}
```
## Usage Notes
- Simple, clean form layout
- Email validation included
- Optional newsletter signup
- Responsive design with side-by-side name/email fields

View File

@ -0,0 +1,342 @@
# Form Builder Agent Instructions
## Agent Purpose
You are a specialized Form Builder Agent that converts User Requirements Specifications (URS) into properly structured JSON form definitions. Your role is to analyze user requirements and generate complete, functional form JSON that can be imported into the Form Builder system.
## Core Capabilities
- **Requirement Analysis**: Parse and understand user requirements
- **Form Structure Design**: Create logical form layouts and field arrangements
- **Component Selection**: Choose appropriate form components for each requirement
- **Validation Rules**: Apply appropriate validation based on field purpose
- **Conditional Logic**: Implement show/hide logic based on user interactions
- **JSON Generation**: Output properly formatted JSON compatible with the Form Builder
## Input Format
Users will provide requirements in various formats:
- **Structured URS**: Detailed specifications with field types, validation, and business rules
- **Natural Language**: Informal descriptions of form needs
- **User Stories**: Agile-style requirements with acceptance criteria
- **Field Lists**: Simple lists of required fields with basic descriptions
## Output Format
Generate JSON that follows the complete form structure specification:
```json
{
"formName": "string",
"formDescription": "string",
"formId": null,
"components": [],
"customScript": "string",
"customCSS": "string",
"formEvents": {},
"scriptMode": "safe"
}
```
## Component Selection Guidelines
### Text Input Components
- **text**: Single-line text input (names, titles, short descriptions)
- **textarea**: Multi-line text input (descriptions, comments, long text)
- **email**: Email address with validation
- **number**: Numeric values with min/max constraints
- **tel**: Phone numbers with formatting
- **url**: Website URLs with validation
- **password**: Secure password input
- **hidden**: Non-visible fields for system data
### Selection Components
- **select**: Dropdown with predefined options
- **radio**: Single choice from multiple options
- **checkbox**: Multiple selections from options
- **switch**: Boolean toggle (yes/no, on/off)
### Date/Time Components
- **date**: Date picker for specific dates
- **time**: Time picker for specific times
- **datetime-local**: Combined date and time
### Advanced Components
- **file**: File upload with type restrictions
- **color**: Color picker for theme selection
- **range**: Slider for numeric ranges
- **repeating-group**: Dynamic list of related fields
- **dynamic-list**: Add/remove items list
- **info-display**: Read-only information display
- **image-preview**: Image display with zoom/caption
### Layout Components
- **heading**: Section titles and headers
- **paragraph**: Descriptive text and instructions
- **divider**: Visual separators between sections
## Validation Rules Mapping
### Common Validation Patterns
- **Required Fields**: `"required"`
- **Email Validation**: `"required|email"`
- **Phone Validation**: `"required|tel"`
- **URL Validation**: `"required|url"`
- **Minimum Length**: `"required|min:3"`
- **Maximum Length**: `"required|max:100"`
- **Numeric Range**: `"required|min:0|max:1000"`
- **Custom Pattern**: `"required|regex:/^[A-Z0-9-]+$/"`
### Business Rule Validation
- **Age Requirements**: `"required|min:18|max:120"`
- **Currency Amounts**: `"required|min:0.01|step:0.01"`
- **Percentage Values**: `"required|min:0|max:100"`
- **Postal Codes**: `"required|regex:/^[0-9]{5}(-[0-9]{4})?$/"`
## Conditional Logic Implementation
### Common Conditional Patterns
- **Show/Hide Based on Selection**:
```json
{
"conditionalLogic": {
"action": "show",
"enabled": true,
"operator": "eq",
"conditions": [
{
"field": "user_type",
"value": "business",
"operator": "eq"
}
]
}
}
```
- **Multiple Conditions**:
```json
{
"conditionalLogic": {
"action": "show",
"enabled": true,
"operator": "and",
"conditions": [
{
"field": "age",
"value": 18,
"operator": "gte"
},
{
"field": "has_license",
"value": true,
"operator": "eq"
}
]
}
}
```
## Grid Layout Guidelines
### Responsive Grid System
- **Full Width**: `"gridColumn": "span 12"`
- **Half Width**: `"gridColumn": "span 6"`
- **Third Width**: `"gridColumn": "span 4"`
- **Quarter Width**: `"gridColumn": "span 3"`
### Layout Patterns
- **Contact Forms**: Name/Email side-by-side, message full-width
- **Address Forms**: Street/Unit, City/State, Postal Code
- **Financial Forms**: Amount/Currency, Date/Reference
- **Multi-step Forms**: Full-width sections with clear headings
## Custom Script Generation
### Common Script Patterns
- **Auto-calculation**:
```javascript
onFieldChange(['quantity', 'price'], () => {
const qty = Number(getField('quantity')) || 0;
const price = Number(getField('price')) || 0;
const total = qty * price;
setField('total', total.toFixed(2));
});
```
- **Conditional Field Management**:
```javascript
onFieldChange('customer_type', (value) => {
if (value === 'business') {
showField('company_name');
showField('tax_id');
} else {
hideField('company_name');
hideField('tax_id');
}
});
```
- **Real-time Validation**:
```javascript
onFieldChange('email', (value) => {
if (value && !value.includes('@')) {
showError('Please enter a valid email address');
}
});
```
## Form Events Configuration
### Event Triggers
- **onLoad**: Execute when form loads
- **onFieldChange**: Execute when any field changes
- **onSubmit**: Execute before form submission
- **onValidation**: Execute during field validation
## Requirement Analysis Process
### Step 1: Parse Requirements
1. Identify form purpose and target users
2. Extract field requirements and data types
3. Identify validation rules and business logic
4. Determine conditional display requirements
5. Note any special formatting or calculation needs
### Step 2: Design Form Structure
1. Create logical field grouping and sections
2. Plan responsive grid layout
3. Design user flow and field order
4. Identify required vs optional fields
5. Plan conditional logic implementation
### Step 3: Component Selection
1. Match field types to appropriate components
2. Configure validation rules
3. Set up conditional logic
4. Add help text and placeholders
5. Configure grid layout properties
### Step 4: Generate JSON
1. Create form metadata (name, description)
2. Build components array with proper structure
3. Add custom scripts for business logic
4. Configure form events
5. Apply styling and layout
## Common Form Templates
### Contact Form Template
- Personal information (name, email, phone)
- Message/Inquiry details
- Contact preferences
- Newsletter signup option
### Registration Form Template
- User credentials (username, password, confirm)
- Personal information
- Terms and conditions acceptance
- Marketing preferences
### Application Form Template
- Personal details
- Professional information
- Supporting documents
- References section
- Declaration and signature
### Survey Form Template
- Multiple choice questions
- Rating scales
- Open-ended responses
- Demographic information
- Progress indicators
## Quality Assurance Checklist
### Before Generating JSON
- [ ] All required fields identified
- [ ] Appropriate validation rules applied
- [ ] Conditional logic properly configured
- [ ] Grid layout responsive and logical
- [ ] Help text and placeholders included
- [ ] Custom scripts handle business logic
- [ ] Form events properly configured
### JSON Validation
- [ ] Valid JSON syntax
- [ ] Required fields present (formName, components)
- [ ] Component structure correct (type, props)
- [ ] Grid values valid (span 1-12)
- [ ] Validation syntax correct
- [ ] Conditional logic field references exist
## Response Format
When generating form JSON, always provide:
1. **Form Analysis**: Brief summary of requirements and design decisions
2. **Component Count**: Number of components created
3. **Special Features**: Any conditional logic, custom scripts, or advanced features
4. **JSON Output**: Complete, properly formatted JSON
5. **Usage Notes**: Any important implementation details or considerations
## Example Response
```
## Form Analysis
Based on your requirements for an employee onboarding form, I've created a comprehensive form with:
- Personal information section (name, contact details)
- Employment details (position, department, start date)
- Document upload section (ID, resume, certifications)
- Emergency contact information
- Conditional logic to show additional fields for managers
## Component Count
Total: 15 components including 3 sections with conditional logic
## Special Features
- Auto-calculates years of experience from start date
- Shows manager-specific fields when "Manager" role is selected
- File upload validation for required documents
- Real-time email format validation
## JSON Output
```json
{
"formName": "Employee Onboarding Form",
"formDescription": "Complete employee registration and onboarding process",
"formId": null,
"components": [
// ... complete JSON structure
],
"customScript": "// Auto-calculate experience\nonFieldChange(['start_date'], () => {\n const startDate = new Date(getField('start_date'));\n const today = new Date();\n const years = Math.floor((today - startDate) / (1000 * 60 * 60 * 24 * 365));\n setField('years_experience', years);\n});",
"formEvents": {
"onLoad": true,
"onFieldChange": true,
"onSubmit": false,
"onValidation": false
},
"scriptMode": "safe"
}
```
## Usage Notes
- Form includes conditional logic for manager-specific fields
- File upload accepts PDF, DOC, and image formats
- Email validation runs in real-time
- Experience calculation updates automatically
```
## Error Handling
### Common Issues and Solutions
1. **Missing Required Fields**: Always include formName and components array
2. **Invalid Component Types**: Use only supported component types
3. **Grid Layout Errors**: Ensure gridColumn values are valid (span 1-12)
4. **Validation Syntax**: Use pipe-separated validation rules
5. **Conditional Logic**: Verify field references exist in the form
### Validation Messages
- Provide clear error messages for invalid requirements
- Suggest alternatives for unsupported features
- Explain any limitations or constraints
- Offer simplified versions for complex requirements
This agent will efficiently convert user requirements into functional, well-structured form JSON that can be directly imported into the Form Builder system.

View File

@ -0,0 +1,738 @@
# Form Builder JSON Structure Documentation
## Overview
This document describes the complete JSON structure used by the Form Builder system for importing and exporting forms. The structure supports the full range of form components, scripts, styling, and metadata.
## Complete Form Structure
### Root Object
```json
{
"formName": "string", // Form name (required)
"formDescription": "string", // Form description (optional)
"formId": "string", // Form UUID (auto-generated when null)
"components": [], // Array of form components (required)
"customScript": "string", // Custom JavaScript code (optional)
"customCSS": "string", // Custom CSS styles (optional)
"formEvents": {}, // Event configuration (optional)
"scriptMode": "string" // Script execution mode (optional)
}
```
### Form Events Configuration
```json
{
"formEvents": {
"onLoad": true, // Execute script when form loads
"onFieldChange": true, // Execute script when fields change
"onSubmit": false, // Execute script before form submission
"onValidation": false // Execute script during field validation
}
}
```
### Script Mode Options
- `"safe"` (default): Scripts run in secure sandbox with form functions only
- `"advanced"`: Broader JavaScript access with security restrictions
## Form Components Array
Each component in the `components` array follows this structure:
### Basic Component Structure
```json
{
"id": "string", // Auto-generated unique ID
"type": "string", // Component type (required)
"props": {} // Component properties (required)
}
```
### Component Types and Props
#### 1. Text Input Components
##### Text Field
```json
{
"type": "text",
"props": {
"name": "field_name", // Field name (required)
"label": "Field Label", // Display label (required)
"placeholder": "Enter text...", // Placeholder text
"help": "Help text", // Help description
"validation": "required|min:3", // Validation rules
"value": "", // Default value
"readonly": false, // Read-only state
"width": "100%", // Field width
"gridColumn": "span 12", // Grid column span
"conditionalLogic": { // Conditional display logic
"action": "show", // show/hide/enable/disable
"enabled": false, // Enable conditional logic
"operator": "and", // and/or for multiple conditions
"conditions": [] // Array of conditions
}
}
}
```
##### Textarea
```json
{
"type": "textarea",
"props": {
"name": "description",
"label": "Description",
"placeholder": "Enter description...",
"help": "Detailed description",
"validation": "required|min:10",
"value": "",
"rows": 4,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Number Field
```json
{
"type": "number",
"props": {
"name": "amount",
"label": "Amount",
"placeholder": "0.00",
"help": "Enter amount",
"validation": "required|min:0",
"value": 0,
"min": 0,
"max": 10000,
"step": 0.01,
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Email Field
```json
{
"type": "email",
"props": {
"name": "email",
"label": "Email Address",
"placeholder": "user@example.com",
"help": "Enter valid email",
"validation": "required|email",
"value": "",
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
#### 2. Selection Components
##### Select Dropdown
```json
{
"type": "select",
"props": {
"name": "category",
"label": "Category",
"help": "Select category",
"validation": "required",
"value": "",
"placeholder": "Choose an option",
"options": [
{ "label": "Option 1", "value": "option_1" },
{ "label": "Option 2", "value": "option_2" }
],
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Radio Buttons
```json
{
"type": "radio",
"props": {
"name": "approval_status",
"label": "Approval Status",
"help": "Select approval decision",
"validation": "required",
"value": "",
"options": [
{ "label": "Approve", "value": "approved" },
{ "label": "Reject", "value": "rejected" },
{ "label": "Pending", "value": "pending" }
],
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Checkboxes
```json
{
"type": "checkbox",
"props": {
"name": "requirements",
"label": "Requirements",
"help": "Select all that apply",
"validation": "",
"value": [],
"options": [
{ "label": "Documentation", "value": "docs" },
{ "label": "Approval", "value": "approval" },
{ "label": "Review", "value": "review" }
],
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Switch Toggle
```json
{
"type": "switch",
"props": {
"name": "is_active",
"label": "Active Status",
"help": "Toggle active status",
"validation": "",
"value": false,
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": { /* same as above */ }
}
}
```
#### 3. Date and Time Components
##### Date Picker
```json
{
"type": "date",
"props": {
"name": "start_date",
"label": "Start Date",
"help": "Select start date",
"validation": "required",
"value": "",
"min": "2024-01-01",
"max": "2025-12-31",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Time Picker
```json
{
"type": "time",
"props": {
"name": "meeting_time",
"label": "Meeting Time",
"help": "Select time",
"validation": "required",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": { /* same as above */ }
}
}
```
##### DateTime Picker
```json
{
"type": "datetime-local",
"props": {
"name": "appointment",
"label": "Appointment",
"help": "Select date and time",
"validation": "required",
"value": "",
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
#### 4. Advanced Input Components
##### File Upload
```json
{
"type": "file",
"props": {
"name": "document",
"label": "Upload Document",
"help": "Upload supporting documents",
"validation": "required",
"accept": ".pdf,.doc,.docx,.jpg,.jpeg,.png",
"multiple": false,
"maxSize": "10MB",
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Color Picker
```json
{
"type": "color",
"props": {
"name": "theme_color",
"label": "Theme Color",
"help": "Select theme color",
"validation": "",
"value": "#3b82f6",
"width": "25%",
"gridColumn": "span 3",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Range Slider
```json
{
"type": "range",
"props": {
"name": "priority",
"label": "Priority Level",
"help": "Set priority level",
"validation": "",
"value": 5,
"min": 1,
"max": 10,
"step": 1,
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Hidden Field
```json
{
"type": "hidden",
"props": {
"name": "user_id",
"value": "12345",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Button
```json
{
"type": "button",
"props": {
"name": "submit_btn",
"label": "Submit Form",
"buttonType": "submit", // submit, button, reset
"variant": "primary", // primary, secondary, danger
"size": "md", // sm, md, lg
"disabled": false,
"width": "25%",
"gridColumn": "span 3",
"conditionalLogic": { /* same as above */ }
}
}
```
#### 5. Layout Components
##### Heading
```json
{
"type": "heading",
"props": {
"name": "section_title",
"value": "Section Title",
"level": 2, // 1-6 heading levels
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Paragraph
```json
{
"type": "paragraph",
"props": {
"name": "description_text",
"value": "This is descriptive text content.",
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Divider
```json
{
"type": "divider",
"props": {
"name": "section_divider",
"style": "solid", // solid, dashed, dotted
"color": "#e5e7eb",
"thickness": 1,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
#### 6. Advanced Components
##### Info Display
```json
{
"type": "info-display",
"props": {
"title": "Information",
"name": "info_display_1",
"help": "Information display",
"layout": "vertical", // vertical, horizontal
"showBorder": true,
"backgroundColor": "#f8fafc",
"fields": [
{ "label": "Status", "value": "Active", "key": "status" },
{ "label": "Created", "value": "2024-01-01", "key": "created" }
],
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Image Preview
```json
{
"type": "image-preview",
"props": {
"label": "Image Preview",
"name": "image_preview_1",
"help": "Preview image",
"imageUrl": "https://placehold.co/600x400",
"altText": "Preview image",
"caption": "Image caption",
"showZoom": true,
"showCaption": true,
"maxWidth": "100%",
"height": "auto",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Repeating Group
```json
{
"type": "repeating-group",
"props": {
"label": "Expense Items",
"name": "expense_items",
"help": "Add expense items",
"minItems": 1,
"maxItems": 10,
"buttonText": "Add Expense",
"removeText": "Remove",
"fields": [
{
"type": "text",
"name": "description",
"label": "Description",
"placeholder": "Enter description"
},
{
"type": "number",
"name": "amount",
"label": "Amount",
"placeholder": "0.00"
}
],
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
##### Dynamic List
```json
{
"type": "dynamic-list",
"props": {
"label": "Tags",
"name": "tags",
"help": "Add tags",
"placeholder": "Enter tag",
"buttonText": "Add Tag",
"minItems": 0,
"maxItems": 20,
"defaultItems": ["Tag 1", "Tag 2"],
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": { /* same as above */ }
}
}
```
## Conditional Logic Structure
### Condition Object
```json
{
"field": "field_name", // Target field name
"value": "expected_value", // Expected value
"operator": "eq" // Comparison operator
}
```
### Available Operators
- `"eq"`: Equal to
- `"ne"`: Not equal to
- `"gt"`: Greater than
- `"gte"`: Greater than or equal to
- `"lt"`: Less than
- `"lte"`: Less than or equal to
- `"contains"`: Contains substring
- `"not_contains"`: Does not contain substring
- `"starts_with"`: Starts with
- `"ends_with"`: Ends with
- `"in"`: Value is in array
- `"not_in"`: Value is not in array
- `"empty"`: Field is empty
- `"not_empty"`: Field is not empty
### Conditional Logic Example
```json
{
"conditionalLogic": {
"action": "show",
"enabled": true,
"operator": "and",
"conditions": [
{
"field": "user_type",
"value": "premium",
"operator": "eq"
},
{
"field": "subscription_status",
"value": "active",
"operator": "eq"
}
]
}
}
```
## Validation Rules
### Common Validation Rules
- `required`: Field is required
- `email`: Valid email format
- `url`: Valid URL format
- `min:n`: Minimum value/length
- `max:n`: Maximum value/length
- `between:min,max`: Value between range
- `alpha`: Only alphabetic characters
- `alphanumeric`: Only alphanumeric characters
- `numeric`: Only numeric values
- `regex:/pattern/`: Custom regex pattern
### Validation Examples
```json
{
"validation": "required|email",
"validation": "required|min:3|max:50",
"validation": "required|numeric|between:1,100",
"validation": "required|regex:/^[A-Z0-9-]+$/"
}
```
## Grid System
### Grid Column Options
- `"span 1"` to `"span 12"`: Column span (1-12)
- `"span 3"`: 25% width (3/12)
- `"span 6"`: 50% width (6/12)
- `"span 9"`: 75% width (9/12)
- `"span 12"`: 100% width (12/12)
### Width Options
- `"25%"`, `"33.33%"`, `"50%"`, `"66.67%"`, `"75%"`, `"100%"`
- Custom pixel values: `"300px"`
- Auto width: `"auto"`
## Complete Form Example
```json
{
"formName": "Travel Reimbursement Form",
"formDescription": "Submit your travel expenses for reimbursement",
"formId": null,
"components": [
{
"type": "heading",
"props": {
"name": "main_heading",
"value": "Travel Reimbursement Request",
"level": 1,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "employee_name",
"label": "Employee Name",
"placeholder": "Enter your full name",
"help": "Your full name as it appears in company records",
"validation": "required|min:2",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "email",
"props": {
"name": "employee_email",
"label": "Email Address",
"placeholder": "employee@company.com",
"help": "Your company email address",
"validation": "required|email",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "number",
"props": {
"name": "total_amount",
"label": "Total Amount (RM)",
"placeholder": "0.00",
"help": "Total reimbursement amount",
"validation": "required|min:0.01",
"value": 0,
"min": 0,
"step": 0.01,
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
}
],
"customScript": "// Auto-calculate total when expenses change\nonFieldChange(['expense_items'], () => {\n const expenses = getField('expense_items') || [];\n const total = expenses.reduce((sum, item) => sum + (parseFloat(item.amount) || 0), 0);\n setField('total_amount', total.toFixed(2));\n});",
"customCSS": ".form-container { background: #f9fafb; padding: 20px; border-radius: 8px; }",
"formEvents": {
"onLoad": true,
"onFieldChange": true,
"onSubmit": false,
"onValidation": false
},
"scriptMode": "safe"
}
```
## Import/Export Usage
### Importing a Form
1. Navigate to Form Management page
2. Click "Import Form" button
3. Select JSON file or paste JSON content
4. Review the imported form structure
5. Save to create the form in database
### Exporting a Form
1. In Form Management, click "Export" on any form
2. JSON file will download with complete form structure
3. Use the exported JSON to backup or duplicate forms
### API Endpoints
- `POST /api/forms/import` - Import form from JSON
- `GET /api/forms/[id]/export` - Export form as JSON
## Best Practices
1. **Naming Convention**: Use descriptive, lowercase field names with underscores
2. **Validation**: Always include appropriate validation rules for data integrity
3. **Grid Layout**: Plan your grid layout for responsive design
4. **Conditional Logic**: Use sparingly to avoid complex dependencies
5. **Default Values**: Provide sensible defaults where appropriate
6. **Help Text**: Include helpful descriptions for complex fields
7. **Component Order**: Logical flow from top to bottom
8. **Testing**: Test forms thoroughly after import
## Troubleshooting
### Common Import Issues
1. **Missing Required Fields**: Ensure `formName` and `components` are present
2. **Invalid JSON**: Validate JSON syntax before import
3. **Component Props**: Each component needs valid `type` and `props`
4. **Grid Values**: Ensure `gridColumn` values are valid (span 1-12)
5. **Validation Syntax**: Check validation rule syntax
6. **Conditional Logic**: Verify field references in conditions exist
### Validation Errors
- Check field name uniqueness
- Verify option arrays for select/radio/checkbox components
- Ensure numeric values for min/max/step properties
- Validate conditional logic field references

View File

@ -0,0 +1,191 @@
{
"formName": "Sample Contact Form",
"formDescription": "A simple contact form with basic fields",
"formId": null,
"components": [
{
"type": "heading",
"props": {
"name": "form_title",
"value": "Contact Us",
"level": 2,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "paragraph",
"props": {
"name": "form_description",
"value": "Please fill out the form below and we'll get back to you as soon as possible.",
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "text",
"props": {
"name": "full_name",
"label": "Full Name",
"placeholder": "Enter your full name",
"help": "Please provide your first and last name",
"validation": "required|min:2",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "email",
"props": {
"name": "email_address",
"label": "Email Address",
"placeholder": "your.email@example.com",
"help": "We'll use this to respond to your inquiry",
"validation": "required|email",
"value": "",
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "select",
"props": {
"name": "inquiry_type",
"label": "Type of Inquiry",
"help": "Select the category that best describes your inquiry",
"validation": "required",
"value": "",
"placeholder": "Choose an option",
"options": [
{ "label": "General Question", "value": "general" },
{ "label": "Technical Support", "value": "support" },
{ "label": "Sales Inquiry", "value": "sales" },
{ "label": "Partnership", "value": "partnership" },
{ "label": "Other", "value": "other" }
],
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "textarea",
"props": {
"name": "message",
"label": "Message",
"placeholder": "Please describe your inquiry in detail...",
"help": "Provide as much detail as possible to help us assist you better",
"validation": "required|min:10",
"value": "",
"rows": 5,
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "checkbox",
"props": {
"name": "contact_preferences",
"label": "Contact Preferences",
"help": "How would you like us to contact you? (Select all that apply)",
"validation": "",
"value": [],
"options": [
{ "label": "Email", "value": "email" },
{ "label": "Phone", "value": "phone" },
{ "label": "SMS", "value": "sms" }
],
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "switch",
"props": {
"name": "newsletter_signup",
"label": "Subscribe to Newsletter",
"help": "Receive updates about our products and services",
"validation": "",
"value": false,
"width": "50%",
"gridColumn": "span 6",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "button",
"props": {
"name": "submit_button",
"label": "Send Message",
"buttonType": "submit",
"variant": "primary",
"size": "md",
"disabled": false,
"width": "25%",
"gridColumn": "span 3",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
}
],
"customScript": "// Auto-format email to lowercase\nonFieldChange(['email_address'], () => {\n const email = getField('email_address');\n if (email) {\n setField('email_address', email.toLowerCase());\n }\n});\n\n// Show success message on form load\nshowInfo('Please fill out all required fields marked with an asterisk (*)');",
"customCSS": ".form-container {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n padding: 20px;\n border-radius: 12px;\n}\n\n.form-field {\n margin-bottom: 16px;\n}",
"formEvents": {
"onLoad": true,
"onFieldChange": true,
"onSubmit": false,
"onValidation": false
},
"scriptMode": "safe"
}

View File

@ -23,7 +23,20 @@
</div> </div>
</div> </div>
<div class="flex items-center"> <div class="flex items-center gap-2">
<!-- Import Form Button with hidden file input -->
<input
type="file"
ref="jsonFileInput"
accept=".json"
style="display: none"
@change="handleJsonImport"
/>
<RsButton @click="showImportModal = true" variant="secondary" size="sm" :disabled="loading">
<Icon name="material-symbols:upload" class="mr-1" />
Import Form
</RsButton>
<RsButton @click="createNewForm" variant="primary" size="sm"> <RsButton @click="createNewForm" variant="primary" size="sm">
<Icon name="material-symbols:add" class="mr-1" /> <Icon name="material-symbols:add" class="mr-1" />
Create New Form Create New Form
@ -159,6 +172,15 @@
<Icon name="material-symbols:content-copy" class="text-lg" /> <Icon name="material-symbols:content-copy" class="text-lg" />
</button> </button>
<button
@click="exportForm(form)"
class="p-1 text-green-600 hover:text-green-900 hover:bg-green-50 rounded"
title="Export Form as JSON"
:disabled="loading"
>
<Icon name="material-symbols:download" class="text-lg" />
</button>
<button <button
@click="confirmDelete(form.id)" @click="confirmDelete(form.id)"
class="p-1 text-red-600 hover:text-red-900 hover:bg-red-50 rounded" class="p-1 text-red-600 hover:text-red-900 hover:bg-red-50 rounded"
@ -233,6 +255,99 @@
</div> </div>
</template> </template>
</RsModal> </RsModal>
<!-- Import Form Modal -->
<RsModal v-model="showImportModal" title="Import Form from JSON" size="lg" position="center">
<div class="p-4">
<div class="mb-4">
<p class="text-gray-600 mb-4">
Import a form by uploading a JSON file or pasting JSON content directly.
The form will be created as a new form in your database.
</p>
<!-- File Upload Option -->
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-700 mb-2">Option 1: Upload JSON File</h3>
<div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center">
<input
type="file"
ref="modalJsonFileInput"
accept=".json"
style="display: none"
@change="handleModalJsonImport"
/>
<Icon name="material-symbols:upload-file" class="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p class="text-sm text-gray-600 mb-3">Click to select a JSON file</p>
<RsButton @click="$refs.modalJsonFileInput.click()" variant="secondary" size="sm">
<Icon name="material-symbols:upload" class="mr-1" />
Select JSON File
</RsButton>
</div>
</div>
<!-- Paste JSON Option -->
<div class="mb-4">
<h3 class="text-sm font-medium text-gray-700 mb-2">Option 2: Paste JSON Content</h3>
<FormKit
v-model="jsonContent"
type="textarea"
placeholder="Paste your form JSON here..."
rows="10"
:classes="{
outer: 'mb-0',
input: 'font-mono text-sm'
}"
/>
</div>
<!-- JSON Validation Status -->
<div v-if="jsonValidationMessage" class="mb-4">
<div
class="p-3 rounded border text-sm"
:class="{
'bg-green-50 border-green-200 text-green-700': jsonIsValid,
'bg-red-50 border-red-200 text-red-700': !jsonIsValid
}"
>
<div class="flex items-center">
<Icon
:name="jsonIsValid ? 'material-symbols:check-circle' : 'material-symbols:error'"
class="w-4 h-4 mr-2"
/>
{{ jsonValidationMessage }}
</div>
</div>
</div>
<!-- Documentation Link -->
<div class="bg-blue-50 border border-blue-200 rounded p-3">
<div class="flex items-start">
<Icon name="material-symbols:info" class="w-4 h-4 text-blue-600 mr-2 mt-0.5" />
<div class="text-sm text-blue-700">
<strong>Need help with the JSON format?</strong>
<br>
Check the complete documentation for the form JSON structure and examples in the project documentation.
</div>
</div>
</div>
</div>
</div>
<template #footer>
<div class="flex justify-end gap-2">
<RsButton @click="closeImportModal" variant="tertiary" :disabled="loading">
Cancel
</RsButton>
<RsButton
@click="importFromPastedJson"
variant="primary"
:disabled="loading || !jsonContent || !jsonIsValid"
>
<Icon v-if="loading" name="material-symbols:progress-activity" class="w-4 h-4 animate-spin mr-1" />
Import Form
</RsButton>
</div>
</template>
</RsModal>
</div> </div>
</template> </template>
@ -276,6 +391,12 @@ const formToDelete = ref(null);
const sortBy = ref('createdAt'); const sortBy = ref('createdAt');
const sortOrder = ref('desc'); const sortOrder = ref('desc');
const showUnsavedChangesModal = ref(false); const showUnsavedChangesModal = ref(false);
const jsonFileInput = ref(null);
const showImportModal = ref(false);
const modalJsonFileInput = ref(null);
const jsonContent = ref('');
const jsonValidationMessage = ref('');
const jsonIsValid = ref(false);
// Filtered forms // Filtered forms
const filteredForms = computed(() => { const filteredForms = computed(() => {
@ -452,6 +573,256 @@ const clearFilters = () => {
onUnmounted(() => { onUnmounted(() => {
clearTimeout(searchTimeout); clearTimeout(searchTimeout);
}); });
// Helper function to get default category for component type
const getDefaultCategory = (type) => {
const categories = {
'text': 'Basic Inputs',
'textarea': 'Basic Inputs',
'number': 'Basic Inputs',
'email': 'Basic Inputs',
'password': 'Basic Inputs',
'select': 'Selection Inputs',
'checkbox': 'Selection Inputs',
'radio': 'Selection Inputs',
'heading': 'Layout',
'paragraph': 'Layout',
'divider': 'Layout',
'file': 'Advanced',
'button': 'Advanced'
};
return categories[type] || 'Basic Inputs';
};
// Helper function to get default icon for component type
const getDefaultIcon = (type) => {
const icons = {
'text': 'material-symbols:text-fields',
'textarea': 'material-symbols:article-outline',
'number': 'material-symbols:counter-1-outline',
'email': 'material-symbols:mail-outline',
'select': 'material-symbols:arrow-drop-down-circle-outline',
'checkbox': 'material-symbols:check-box-outline',
'radio': 'material-symbols:radio-button-checked-outline',
'heading': 'material-symbols:title',
'paragraph': 'material-symbols:text-snippet-outline',
'file': 'material-symbols:upload-file-outline',
'button': 'material-symbols:smart-button'
};
return icons[type] || 'material-symbols:add';
};
// Handle JSON file import
const handleJsonImport = async (event) => {
const file = event.target.files[0];
if (!file) return;
loading.value = true;
try {
const reader = new FileReader();
reader.onload = async (e) => {
try {
const importedJson = JSON.parse(e.target.result);
// Validate imported JSON structure
if (!importedJson.formName || !Array.isArray(importedJson.components)) {
throw new Error('Invalid form JSON structure. Must include formName and components array.');
}
// Create the form in the database using the import API
const response = await fetch('/api/forms/import', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(importedJson)
});
const result = await response.json();
if (result.success) {
// Refresh the form list
await loadForms();
toast.success(`Form "${importedJson.formName}" imported successfully`);
} else {
throw new Error(result.error || 'Failed to import form');
}
} catch (err) {
console.error('Error processing imported JSON:', err);
toast.error(`Failed to import form: ${err.message}`);
} finally {
loading.value = false;
// Reset file input
event.target.value = '';
}
};
reader.onerror = () => {
toast.error('Error reading file');
loading.value = false;
event.target.value = '';
};
reader.readAsText(file);
} catch (error) {
console.error('Error importing form:', error);
toast.error('Failed to import form: ' + error.message);
loading.value = false;
event.target.value = '';
}
};
// Export form as JSON
const exportForm = async (form) => {
try {
loading.value = true;
// Fetch the complete form data
const response = await fetch(`/api/forms/${form.id}`);
const result = await response.json();
if (result.success) {
const formData = result.form;
// Create the export JSON structure
const exportJson = {
formName: formData.formName,
formDescription: formData.formDescription || '',
formId: null, // Set to null for import as new form
components: formData.formComponents || [],
customScript: formData.customScript || '',
customCSS: formData.customCSS || '',
formEvents: formData.formEvents || {
onLoad: false,
onFieldChange: false,
onSubmit: false,
onValidation: false
},
scriptMode: formData.scriptMode || 'safe'
};
// Create and download the JSON file
const jsonBlob = new Blob([JSON.stringify(exportJson, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(jsonBlob);
const link = document.createElement('a');
link.href = url;
link.download = `${formData.formName.replace(/\s+/g, '-').toLowerCase()}_${new Date().toISOString().split('T')[0]}.json`;
link.click();
URL.revokeObjectURL(url);
toast.success(`Form "${formData.formName}" exported successfully`);
} else {
throw new Error(result.error || 'Failed to fetch form data');
}
} catch (error) {
console.error('Error exporting form:', error);
toast.error(`Failed to export form: ${error.message || 'Unknown error'}`);
} finally {
loading.value = false;
}
};
// Watch for JSON content changes to validate
watch(jsonContent, (newContent) => {
if (!newContent.trim()) {
jsonValidationMessage.value = '';
jsonIsValid.value = false;
return;
}
try {
const parsed = JSON.parse(newContent);
// Validate basic structure
if (!parsed.formName) {
jsonValidationMessage.value = 'JSON is valid but missing required "formName" field';
jsonIsValid.value = false;
return;
}
if (!Array.isArray(parsed.components)) {
jsonValidationMessage.value = 'JSON is valid but missing required "components" array';
jsonIsValid.value = false;
return;
}
jsonValidationMessage.value = `Valid form JSON with ${parsed.components.length} components`;
jsonIsValid.value = true;
} catch (error) {
jsonValidationMessage.value = `Invalid JSON: ${error.message}`;
jsonIsValid.value = false;
}
});
// Handle modal JSON file import
const handleModalJsonImport = async (event) => {
const file = event.target.files[0];
if (!file) return;
try {
const reader = new FileReader();
reader.onload = (e) => {
jsonContent.value = e.target.result;
// Reset file input
event.target.value = '';
};
reader.readAsText(file);
} catch (error) {
toast.error('Error reading file: ' + error.message);
event.target.value = '';
}
};
// Import from pasted JSON content
const importFromPastedJson = async () => {
if (!jsonContent.value || !jsonIsValid.value) {
toast.error('Please provide valid JSON content');
return;
}
loading.value = true;
try {
const importedJson = JSON.parse(jsonContent.value);
// Create the form in the database using the import API
const response = await fetch('/api/forms/import', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(importedJson)
});
const result = await response.json();
if (result.success) {
// Refresh the form list
await loadForms();
toast.success(`Form "${importedJson.formName}" imported successfully`);
closeImportModal();
} else {
throw new Error(result.error || 'Failed to import form');
}
} catch (err) {
console.error('Error importing form:', err);
toast.error(`Failed to import form: ${err.message}`);
} finally {
loading.value = false;
}
};
// Close import modal and reset state
const closeImportModal = () => {
showImportModal.value = false;
jsonContent.value = '';
jsonValidationMessage.value = '';
jsonIsValid.value = false;
};
</script> </script>
<style scoped> <style scoped>

View File

@ -0,0 +1,220 @@
import { PrismaClient } from '@prisma/client';
import { v4 as uuidv4 } from 'uuid';
// Initialize Prisma client
const prisma = new PrismaClient();
export default defineEventHandler(async (event) => {
try {
// Parse the request body
const body = await readBody(event);
// Validate required fields
if (!body.formName) {
return {
success: false,
error: 'Form name is required'
};
}
if (!Array.isArray(body.components)) {
return {
success: false,
error: 'Components array is required'
};
}
// Process and validate components
const processedComponents = [];
body.components.forEach((component, index) => {
if (!component.type || !component.props) {
console.warn(`Skipping invalid component at index ${index}:`, component);
return;
}
// Process component properties based on type
let processedProps = { ...component.props };
// Ensure required grid properties
if (!processedProps.width) {
processedProps.width = '100%';
}
if (!processedProps.gridColumn) {
processedProps.gridColumn = 'span 12';
}
// Handle special component types and ensure required properties
switch (component.type) {
case 'image-preview':
processedProps = {
label: processedProps.label || 'Image Preview',
name: processedProps.name || `image_preview_${index + 1}`,
help: processedProps.help || '',
imageUrl: processedProps.imageUrl || 'https://placehold.co/600x400',
altText: processedProps.altText || 'Preview image',
caption: processedProps.caption || '',
showZoom: processedProps.showZoom !== undefined ? processedProps.showZoom : true,
showCaption: processedProps.showCaption !== undefined ? processedProps.showCaption : true,
maxWidth: processedProps.maxWidth || '100%',
height: processedProps.height || 'auto',
...processedProps
};
break;
case 'repeating-group':
processedProps = {
label: processedProps.label || 'Repeating Group',
name: processedProps.name || `repeating_group_${index + 1}`,
help: processedProps.help || '',
minItems: processedProps.minItems !== undefined ? processedProps.minItems : 1,
maxItems: processedProps.maxItems !== undefined ? processedProps.maxItems : 10,
buttonText: processedProps.buttonText || 'Add Item',
removeText: processedProps.removeText || 'Remove',
fields: processedProps.fields || [
{ type: 'text', name: 'field_1', label: 'Field 1', placeholder: 'Enter value' }
],
...processedProps
};
break;
case 'dynamic-list':
processedProps = {
label: processedProps.label || 'Dynamic List',
name: processedProps.name || `dynamic_list_${index + 1}`,
help: processedProps.help || '',
placeholder: processedProps.placeholder || 'Enter item',
buttonText: processedProps.buttonText || 'Add Item',
minItems: processedProps.minItems !== undefined ? processedProps.minItems : 0,
maxItems: processedProps.maxItems !== undefined ? processedProps.maxItems : 20,
defaultItems: Array.isArray(processedProps.defaultItems) ? processedProps.defaultItems : ['Item 1', 'Item 2'],
...processedProps
};
break;
case 'info-display':
processedProps = {
title: processedProps.title || 'Information',
name: processedProps.name || `info_display_${index + 1}`,
help: processedProps.help || '',
layout: processedProps.layout || 'vertical',
showBorder: processedProps.showBorder !== undefined ? processedProps.showBorder : true,
backgroundColor: processedProps.backgroundColor || '#f8fafc',
fields: Array.isArray(processedProps.fields) ? processedProps.fields : [
{ label: 'Info Item', value: 'Value', key: 'item_1' }
],
...processedProps
};
break;
case 'file':
processedProps = {
label: processedProps.label || 'File Upload',
name: processedProps.name || `file_upload_${index + 1}`,
help: processedProps.help || 'Upload a file',
accept: processedProps.accept || '.pdf,.doc,.docx,.jpg,.jpeg,.png',
...processedProps
};
break;
case 'heading':
processedProps = {
value: processedProps.value || 'Heading',
level: processedProps.level || 2,
...processedProps
};
break;
case 'paragraph':
processedProps = {
value: processedProps.value || 'Paragraph text',
...processedProps
};
break;
case 'select':
case 'radio':
case 'checkbox':
// Ensure options array exists
if (!Array.isArray(processedProps.options) || processedProps.options.length === 0) {
processedProps.options = [
{ label: 'Option 1', value: 'option_1' },
{ label: 'Option 2', value: 'option_2' }
];
}
break;
default:
// Basic properties for all other component types
if (!processedProps.label && !['heading', 'paragraph', 'divider'].includes(component.type)) {
processedProps.label = component.type.charAt(0).toUpperCase() + component.type.slice(1) + ' ' + (index + 1);
}
if (!processedProps.name && !['heading', 'paragraph', 'divider'].includes(component.type)) {
processedProps.name = `${component.type}_${index + 1}`;
}
break;
}
// Add processed component
processedComponents.push({
type: component.type,
props: processedProps
});
});
// Create a new form in the database
const form = await prisma.form.create({
data: {
formUUID: uuidv4(),
formName: body.formName,
formDescription: body.formDescription || null,
formComponents: processedComponents,
formStatus: 'active',
formCreatedBy: body.createdBy || null, // In a real app, this would come from the authenticated user
customScript: body.customScript || null,
customCSS: body.customCSS || null,
formEvents: body.formEvents || null,
scriptMode: body.scriptMode || 'safe'
}
});
// Create history entry for form creation
if (form.formID) {
try {
await prisma.formHistory.create({
data: {
formHistoryFormID: form.formID,
formHistoryAction: 'created_from_import',
formHistoryData: {
formName: form.formName,
formDescription: form.formDescription,
componentCount: processedComponents.length,
importSource: 'json_import'
},
formHistorySavedBy: body.createdBy || null,
formHistoryChangeDescription: 'Form created from JSON import'
}
});
} catch (historyError) {
console.warn('Failed to create history entry:', historyError);
// Don't fail the import if history creation fails
}
}
return {
success: true,
form,
message: `Form "${form.formName}" imported successfully with ${processedComponents.length} components`
};
} catch (error) {
console.error('Error importing form:', error);
return {
success: false,
error: 'Failed to import form',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
};
}
});