Successfully migrated all forms in the WebUI service to use the new @penguin/react-libs FormBuilder component.
Before: 206 lines → After: 180 lines (26 lines removed, 13% reduction)
Changes:
- Removed manual form state management (
newUserstate object) - Removed manual input handlers and validation
- Removed 60+ lines of modal HTML markup
- Replaced with
<FormBuilder mode="modal" />(11 lines)
Key Improvements:
- Built-in email validation
- Password minimum length validation with helper text
- Automatic error handling
- Loading state management
- Auto-focus on first field
Before: 186 lines → After: 174 lines (12 lines removed, 6% reduction)
Changes:
- Removed manual form state management (
formDatastate object) - Removed grid layout HTML with individual input fields
- Replaced with
<FormBuilder mode="inline" />with field configuration - Simplified submit handler (removed form state updates)
Key Improvements:
- Field-level configuration for grid layout
- Boolean to string conversion for select field handled
- Optional password field (only sent if provided)
- Initial data pre-filling from user object
Before: 209 lines → After: 171 lines (38 lines removed, 18% reduction)
Changes:
- Removed manual form state management
- Removed 70+ lines of form HTML (basic info + password fields)
- Replaced with
<FormBuilder mode="inline" />in edit mode - Simplified toggle between view and edit modes
Key Improvements:
- Disabled email field with helper text
- Password confirmation validation
- Helper text for all password fields
- Cleaner edit/view toggle logic
- Total lines removed: 76 lines across 3 files (12% reduction)
- Code duplication eliminated: 100+ lines of repeated form patterns
- Consistency: All forms now use the same validation and styling
- Maintainability: Field configuration is declarative and reusable
{showCreateModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="card w-full max-w-md">
<h2 className="text-xl font-bold text-gold-400 mb-4">Create New User</h2>
<form onSubmit={handleCreateUser} className="space-y-4">
<div>
<label className="block text-sm text-dark-400 mb-1">Full Name</label>
<input
type="text"
value={newUser.full_name}
onChange={(e) => setNewUser({ ...newUser, full_name: e.target.value })}
className="input"
required
/>
</div>
{/* 3 more fields... */}
<div className="flex justify-end gap-3 mt-6">
<Button type="button" variant="secondary" onClick={() => setShowCreateModal(false)}>
Cancel
</Button>
<Button type="submit" isLoading={createLoading}>
Create User
</Button>
</div>
</form>
</div>
</div>
)}<FormBuilder
mode="modal"
isOpen={showCreateModal}
fields={userFields}
title="Create New User"
submitLabel="Create User"
cancelLabel="Cancel"
onSubmit={handleCreateUser}
onCancel={() => setShowCreateModal(false)}
error={error}
/><Card>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm text-dark-400 mb-1">Full Name</label>
<input
type="text"
value={formData.full_name || ''}
onChange={(e) => setFormData({ ...formData, full_name: e.target.value })}
className="input"
required
/>
</div>
{/* 4 more fields... */}
</div>
{/* Actions */}
</form>
</Card><Card>
<FormBuilder
mode="inline"
fields={getUserEditFields(true)}
initialData={{
full_name: user.full_name,
email: user.email,
role: user.role,
is_active: String(user.is_active),
}}
submitLabel="Save Changes"
cancelLabel="Cancel"
onSubmit={handleSubmit}
onCancel={() => navigate('/users')}
error={error}
className="grid grid-cols-1 md:grid-cols-2 gap-6"
/>
</Card>{
name: 'full_name',
label: 'Full Name',
type: 'text',
required: true,
placeholder: 'John Doe',
autoFocus: true,
}{
name: 'email',
label: 'Email',
type: 'email',
required: true,
placeholder: 'user@example.com',
}{
name: 'password',
label: 'Password',
type: 'password',
required: true,
minLength: 8,
helperText: 'Minimum 8 characters required',
}{
name: 'role',
label: 'Role',
type: 'select',
required: true,
defaultValue: 'viewer',
options: [
{ value: 'viewer', label: 'Viewer' },
{ value: 'maintainer', label: 'Maintainer' },
{ value: 'admin', label: 'Admin' },
],
}{
name: 'email',
label: 'Email',
type: 'email',
disabled: true,
helperText: 'Contact admin to change email',
}- 76 lines removed across 3 files
- 100+ lines of duplicate form patterns eliminated
- Cleaner, more focused component code
- All forms use identical styling (Tailwind classes)
- Consistent error handling and display
- Uniform validation behavior
- Same loading state UI
- Declarative field configuration vs imperative form building
- Type-safe field definitions with TypeScript
- No manual state management for form values
- Built-in validation rules (email, minLength, required, etc.)
- Single source of truth for form behavior
- Changes to FormBuilder automatically apply to all forms
- Easy to add new fields (just add to config array)
- Reusable field configurations
- Consistent validation feedback
- Accessible form fields (labels, ARIA attributes)
- Keyboard navigation support (ESC to close modal, Tab order)
- Loading indicators during submission
- Error messages displayed consistently
- Auto-focus on first field (modal forms)
- Body scroll lock when modal open
- Click outside to close modal
- ESC key to close modal
- Helper text for fields
- Field-level validation errors
- Form-level error display
-
Additional Field Types
- File upload
- Multi-select
- Date range picker
- Rich text editor
-
Advanced Validation
- Cross-field validation
- Async validation (check if email exists)
- Custom error messages per field
-
Conditional Fields
- Show/hide fields based on other field values
- Dynamic field lists
-
Form Sections
- Collapsible sections
- Multi-step forms (wizard)
- Progress indicators
-
Auto-save
- Draft saving
- Restore on page reload
When creating new forms or migrating existing ones:
- Define field configuration array
- Choose mode:
modalorinline - Set initial data (for edit forms)
- Handle submit (receives data object)
- Handle cancel (close modal / navigate away)
- Display errors (pass
errorprop) - Test validation (required fields, email, password length)
- Test submission (loading state, success, error)
- Test modal behavior (ESC, click outside)
- Verify accessibility (keyboard navigation, screen readers)
The FormBuilder migration has successfully:
- ✅ Reduced code duplication by 100+ lines
- ✅ Improved consistency across all forms
- ✅ Enhanced user experience with built-in features
- ✅ Made forms easier to maintain and extend
- ✅ Provided type-safe, declarative API for form building
All three major forms in the WebUI now use the FormBuilder component, demonstrating its flexibility for both modal and inline use cases.