Files
beenvoice/docs/forms-guide.md
Sean O'Connor f331136090 feat: polish invoice editor and viewer UI with custom NumberInput
component

- Create custom NumberInput component with increment/decrement buttons
- Add 0.25 step increments for hours and rates in invoice forms
- Implement emerald-themed styling with hover states and accessibility
- Add keyboard navigation (arrow keys) and proper ARIA support
- Condense invoice editor tax/totals section into efficient grid layout
- Update client dropdown to single-line format (name + email)
- Add fixed footer with floating action bar pattern matching business
  forms
- Redesign invoice viewer with better space utilization and visual
  hierarchy
- Maintain professional appearance and consistent design system
- Fix Next.js 15 params Promise handling across all invoice pages
- Resolve TypeScript compilation errors and type-only imports
2025-07-15 00:29:02 -04:00

7.8 KiB

Forms Improvement Guide

Overview

The business and client creation/editing forms have been significantly improved with better organization, shared components, enhanced validation, and improved user experience.

Key Improvements

1. Shared Components & Utilities

Address Form Component (src/components/ui/address-form.tsx)

A reusable address form component that handles:

  • Country-aware formatting (US ZIP codes, Canadian postal codes)
  • State dropdown for US addresses, text input for other countries
  • Popular countries listed first in country dropdown
  • Automatic field adjustments based on country selection
<AddressForm
  addressLine1={formData.addressLine1}
  addressLine2={formData.addressLine2}
  city={formData.city}
  state={formData.state}
  postalCode={formData.postalCode}
  country={formData.country}
  onChange={handleInputChange}
  errors={errors}
  required={false}
/>

Form Constants & Utilities (src/lib/form-constants.ts)

Centralized location for:

  • US states list with proper formatting
  • All countries with ISO codes
  • Popular countries for quick selection
  • Format functions for phone, postal codes, tax IDs, and URLs
  • Validation utilities and messages

2. Enhanced Form Validation

Real-time Validation

  • Errors clear as soon as user starts typing
  • Field-specific validation messages
  • Visual feedback with red borders on invalid fields

Smart Validation Rules

  • Email: Proper email format checking
  • Phone: US phone number format validation
  • Address: Required fields only if any address field is filled
  • URL: Automatic https:// prefix addition
// Example validation
if (formData.email && !isValidEmail(formData.email)) {
  newErrors.email = VALIDATION_MESSAGES.email;
}

3. Better Form Organization

Card-based Sections

Forms are now organized into logical sections using cards:

  • Basic Information: Core fields like name, tax ID
  • Contact Information: Email, phone, website
  • Address: Complete address form with smart country handling
  • Settings: Business-specific settings like default business flag

Consistent Layout

  • Maximum width container for better readability
  • Responsive grid layouts that stack on mobile
  • Proper spacing between sections
  • Clear visual hierarchy

4. Improved User Experience

Loading States

  • Skeleton loader while fetching data in edit mode
  • Disabled form fields during submission
  • Loading spinner in submit button

Unsaved Changes Warning

const handleCancel = () => {
  if (isDirty) {
    const confirmed = window.confirm(
      "You have unsaved changes. Are you sure you want to leave?"
    );
    if (!confirmed) return;
  }
  router.push("/dashboard/businesses");
};

Smart Field Formatting

  • Phone numbers: Auto-format as (555) 123-4567
  • Tax ID: Auto-format as 12-3456789
  • Postal codes: Format based on country (US vs Canadian)
  • Website URLs: Auto-add https:// if missing

5. Responsive Design

Mobile Optimizations

  • Form sections stack vertically on small screens
  • Touch-friendly input sizes
  • Proper button positioning
  • Readable font sizes

Desktop Enhancements

  • Two-column layouts for related fields
  • Optimal reading width
  • Side-by-side form actions

6. Code Reusability

Shared Between Business & Client Forms

  • Address form component
  • Validation logic
  • Format functions
  • Constants (states, countries)
  • Error handling patterns

TypeScript Interfaces

interface FormData {
  name: string;
  email: string;
  phone: string;
  // ... other fields
}

interface FormErrors {
  name?: string;
  email?: string;
  // ... validation errors
}

Usage Examples

Basic Form Implementation

export function BusinessForm({ businessId, mode }: BusinessFormProps) {
  const [formData, setFormData] = useState<FormData>(initialFormData);
  const [errors, setErrors] = useState<FormErrors>({});
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [isDirty, setIsDirty] = useState(false);

  // Handle input changes
  const handleInputChange = (field: string, value: string | boolean) => {
    setFormData((prev) => ({ ...prev, [field]: value }));
    setIsDirty(true);
    
    // Clear error when user types
    if (errors[field as keyof FormErrors]) {
      setErrors((prev) => ({ ...prev, [field]: undefined }));
    }
  };

  // Validate and submit
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    if (!validateForm()) {
      toast.error("Please correct the errors in the form");
      return;
    }
    
    // Submit logic...
  };
}

Field with Icon and Validation

<div className="space-y-2">
  <Label htmlFor="email">
    Email
    <span className="text-muted-foreground ml-1 text-xs">(Optional)</span>
  </Label>
  <div className="relative">
    <Mail className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
    <Input
      id="email"
      type="email"
      value={formData.email}
      onChange={(e) => handleInputChange("email", e.target.value)}
      placeholder={PLACEHOLDERS.email}
      className={`pl-10 ${errors.email ? "border-destructive" : ""}`}
      disabled={isSubmitting}
    />
  </div>
  {errors.email && (
    <p className="text-sm text-destructive">{errors.email}</p>
  )}
</div>

Best Practices

1. Form State Management

  • Use controlled components for all inputs
  • Track dirty state for unsaved changes warnings
  • Clear errors when user corrects them
  • Disable form during submission

2. Validation Strategy

  • Validate on submit, not on blur (less annoying)
  • Clear errors immediately when user starts fixing them
  • Show field-level errors below each input
  • Use consistent error message format

3. Accessibility

  • Proper label associations with htmlFor
  • Required field indicators
  • Error messages linked to fields
  • Keyboard navigation support
  • Focus management

4. Performance

  • Memoize expensive computations
  • Use debouncing for format functions if needed
  • Lazy load country lists
  • Optimize re-renders with proper state management

Migration Guide

From Old Forms

  1. Replace inline state/country arrays with imported constants
  2. Use AddressForm component instead of individual address fields
  3. Apply format functions from form-constants.ts
  4. Update validation to use shared utilities
  5. Wrap sections in Card components
  6. Add loading and dirty state tracking

Example Migration

// Before
const US_STATES = [
  { value: "AL", label: "Alabama" },
  // ... duplicated in each form
];

// After
import { US_STATES, formatPhoneNumber } from "~/lib/form-constants";
import { AddressForm } from "~/components/ui/address-form";

Future Enhancements

Planned Improvements

  1. Field-level permissions: Disable fields based on user role
  2. Auto-save: Save draft as user types
  3. Multi-step forms: Break long forms into steps
  4. Conditional fields: Show/hide fields based on other values
  5. Bulk operations: Create multiple records at once
  6. Import from templates: Pre-fill common business types

Extensibility

The form system is designed to be easily extended:

  • Add new format functions to form-constants.ts
  • Create additional shared form components
  • Extend validation rules as needed
  • Add new field types with consistent patterns

Troubleshooting

Common Issues

  1. Validation not working: Ensure field names match FormErrors interface
  2. Format function not applying: Check that onChange uses the format function
  3. Country dropdown not searching: Verify SearchableSelect has search enabled
  4. Address validation failing: Check if country field affects validation rules

Debug Tips

  • Use React DevTools to inspect form state
  • Check console for validation errors
  • Verify API responses match expected format
  • Test with different country selections