diff --git a/BLOCKS_DEPRECATION_STRATEGY.md b/BLOCKS_DEPRECATION_STRATEGY.md new file mode 100644 index 0000000..3a9cfc6 --- /dev/null +++ b/BLOCKS_DEPRECATION_STRATEGY.md @@ -0,0 +1,321 @@ +# Block System Deprecation Strategy + +**Status**: Research & Planning Phase +**Date**: 2025-10-24 +**Purpose**: Plan migration from custom Block templating engine to standard Rails views + +--- + +## Current Block System Architecture + +### What Are Blocks? + +Blocks are PISTN's custom templating layer that allows CSRs to modify dealer-specific HTML/CSS/JS without code changes. + +**Core Components**: +- **Block.rb** (1014 lines) - Hierarchical rendering engine +- **BlockSetting.rb** - Configuration & image handling +- **blocks.js** - Frontend behaviors +- **Admin UI** - Block editor interface + +### How Blocks Work + +#### 1. Hierarchical Structure (acts_as_tree) +```ruby +# Parent-child relationship with roles +parent_block + ├── child_block (role: "header") + ├── child_block (role: "content") + └── child_block (role: "footer") +``` + +#### 2. Template Substitution ({{...}} syntax) +```html + +

{{account.name}}

+ + +{{if account.has_appointments?}} +
{{yield.appointments}}
+{{/if}} + + +{{account.is_active? ? 'Open' : 'Closed'}} + + +{{yield.services.each do |service|}} +
{{service.name}}
+{{/yield.services.each}} +``` + +#### 3. Whitelist Security +```ruby +# Only whitelisted methods can be called +WHITELIST = { + 'account' => %w(name phone_number services ...), + 'block' => %w(image_url ...), + 'ANY' => %w(present? blank? to_s zero?) +} +``` + +#### 4. Account-Specific Content +- Universal blocks: `account_id = NULL` +- Account-specific: `account_id = specific_account` +- Group inheritance: Accounts inherit parent group's blocks +- Priority: Account-specific > Group > Universal + +--- + +## Why Deprecate Blocks? + +### Pain Points + +#### Developer Experience +- ❌ 1000+ lines of state machine rendering logic +- ❌ Three languages in one file: HTML + Handlebars + Ruby +- ❌ No IDE support (syntax highlighting, autocomplete) +- ❌ Difficult to debug template logic + +#### Maintenance & Testing +- ❌ Complex caching logic (cache invalidation bugs) +- ❌ Hard to unit test (requires full render context) +- ❌ N+1 query potential in template evaluation +- ❌ Security audits need constant whitelist review + +#### Performance +- ❌ Interpretation layer (regex parsing, method lookup) +- ❌ Heavy context object passing +- ❌ Caching complexity adds overhead + +#### User Experience (CSRs) +- ❌ Admin UI for block editing is clunky +- ❌ Learning curve (handlebars syntax) +- ❌ No real-time preview +- ❌ Error messages unhelpful + +#### Architecture +- ⚠️ **Views Still Required**: Rails views exist anyway (blocks are a layer on top) +- ⚠️ **Duplication**: Both block template + Rails view exist +- ⚠️ **Migration Complexity**: Must maintain dual rendering for transition + +--- + +## Migration Strategy + +### Phase 1: Analysis & Mapping (Current) + +**Tasks**: +1. ✅ Understand Block.rb rendering engine (done) +2. Identify all active blocks in production +3. Categorize blocks: + - Simple HTML passthrough + - Moderate complexity (some conditionals) + - Complex (loops, dynamic content) +4. Map each block to Rails view equivalent +5. Document {{}} syntax usage patterns +6. Identify which blocks use advanced features + +**Block 112 Case Study** (Appointment Form): +``` +TODO: Query production database +- Block structure +- {{}} syntax patterns +- Block settings used +- Conditional rendering logic +- Child blocks/context +``` + +### Phase 2: Create Replacement Views + +**For Each Block Type**: + +1. **Create Rails Partial/View** + ```erb + + <%= form_for @appointment do |f| %> + <%= f.text_field :date %> + <%= f.text_field :time %> + <%= f.submit %> + <% end %> + ``` + +2. **Move Whitelist Logic to Presenter** + ```ruby + # app/presenters/account_presenter.rb + class AccountPresenter + def initialize(account) + @account = account + end + + def appointment_link + # Only include if account has appointments enabled + link_to "Book Appointment", appointment_path if @account.appointments_enabled? + end + end + ``` + +3. **Use Rails Helpers** + - Form helpers (form_for, form_with) + - Link helpers (link_to, button_to) + - Conditional helpers (if, unless) + - Loop helpers (each, collect) + +### Phase 3: Gradual Migration + +**Feature Flag Approach**: + +```ruby +# config/initializers/feature_flags.rb +FEATURE_FLAGS = { + use_rails_views: false # Start as false, enable per account +} + +# In views/controllers +if account.use_rails_views? + render :appointment_form_v2 +else + render_block(112) # Old block system +end +``` + +**Per-Account Migration**: +- Add boolean `use_rails_views` to Account model +- Gradual rollout: test accounts → all dealers +- Monitor performance & error rates +- Easy rollback if issues + +### Phase 4: Decommission + +**Remove Block System**: +1. Delete Block rendering code +2. Remove block admin UI +3. Drop blocks/block_settings tables +4. Delete Block & BlockSetting models +5. Remove {{}} template syntax processor +6. Clean up views that use render_block() + +--- + +## Implementation Steps + +### Step 1: Research & Planning +- [ ] Query block 112 from production +- [ ] Document all {{}} patterns used +- [ ] Identify all active blocks +- [ ] Categorize by complexity +- [ ] Create mapping document + +### Step 2: Build Replacement Views +- [ ] Create V2 views in app/views/account_specific/ +- [ ] Create presenters in app/presenters/ +- [ ] Move authorization logic to policies +- [ ] Create helpers for common patterns + +### Step 3: Feature Flag & Testing +- [ ] Add `use_rails_views` to Account +- [ ] Implement feature flag logic +- [ ] Test with sample accounts +- [ ] Performance testing +- [ ] Error monitoring + +### Step 4: Rollout +- [ ] Internal team testing +- [ ] Beta test with select dealers +- [ ] Gradual rollout to all accounts +- [ ] Monitor metrics +- [ ] Collect feedback + +### Step 5: Cleanup +- [ ] Remove block system code +- [ ] Delete admin UI +- [ ] Drop database tables +- [ ] Update documentation + +--- + +## Benefits After Migration + +| Aspect | Before (Blocks) | After (Rails Views) | +|--------|-----------------|-------------------| +| **Code** | Custom 1000+ line engine | Standard Rails patterns | +| **Language** | HTML + Handlebars + Ruby | ERB only | +| **Testing** | Integration tests required | Standard view tests | +| **IDE** | No support | Full syntax highlighting | +| **Performance** | Interpretation layer | Direct rendering | +| **Security** | Massive whitelist | Rails authorization | +| **Debugging** | Context tracing | Standard stack traces | +| **CSR UX** | Block admin UI | Standard Rails admin (ActiveAdmin, etc) | +| **Onboarding** | Learn handlebars syntax | Standard Rails knowledge | + +--- + +## Risk Mitigation + +### Risks + +**Performance**: Template rendering much slower? +- Mitigation: Rails view caching (fragment caching, etags) +- Testing: Benchmark before/after + +**Breaking Changes**: Block customizations that can't translate? +- Mitigation: Identify edge cases in Phase 1 +- Fallback: Keep Block system for complex accounts + +**CSR Disruption**: CSRs dependent on block editor? +- Mitigation: Train on new Rails admin interface +- Timeline: Gradual rollout (6+ months) + +**Database Migration**: Data loss from dropping tables? +- Mitigation: Archive block data before deletion +- Backup: Keep block definitions in version control + +### Contingency Plans + +1. If too many blocks use advanced features: + - Keep blocks for "legacy mode" + - Migrate new features to Rails views + - Hybrid approach long-term + +2. If performance regression: + - Implement aggressive view caching + - Use Redis for cache + - Defer to background jobs + +3. If CSR adoption slow: + - Extend timeline + - Provide better training + - Create CSR-facing abstraction layer + +--- + +## Next Steps + +1. **This Session**: + - Query block 112 structure + - Document current {{}} syntax patterns + - Identify block complexity categories + +2. **Next Session**: + - Create first V2 view (appointment form) + - Build presenter/helper structure + - Implement feature flag logic + +3. **Future**: + - Beta testing with select accounts + - Performance optimization + - Full rollout plan + +--- + +## Resources + +- Block.rb: 1014 lines, rendering logic in `render()` and `subbed_content()` +- BlockSetting.rb: Configuration and image handling +- BLOCKS_SYSTEM_GUIDE.md: Comprehensive architecture documentation +- Database: blocks, block_settings, accounts tables + +--- + +**Status**: Ready for Phase 1 Analysis +**Owner**: Engineering Team +**Timeline**: 3-6 months estimated diff --git a/BLOCK_CASE_STUDY_TUFFY_APPOINTMENT_FORM.md b/BLOCK_CASE_STUDY_TUFFY_APPOINTMENT_FORM.md new file mode 100644 index 0000000..a1f054c --- /dev/null +++ b/BLOCK_CASE_STUDY_TUFFY_APPOINTMENT_FORM.md @@ -0,0 +1,1142 @@ +# Block System Case Study: Tuffy Appointment Form (Block 122) +## Migration Blueprint for Rails Views Refactor + +**Date**: 2025-10-24 +**Block ID**: 122 (Tuffy Appt Main) +**Account ID**: 11187 (Account-specific block) +**Status**: Real production block analyzed + +--- + +## Executive Summary + +Block 122 is **THE complex appointment scheduling form** - representing the highest complexity in PISTN's Block system. It demonstrates: + +- ✅ 43KB of mixed HTML, Handlebars templating, and inline JavaScript +- ✅ 4-step wizard workflow with client-side state management +- ✅ Complex conditional rendering based on account settings +- ✅ Inline JavaScript with jQuery and event handlers +- ✅ Form validation with business logic +- ✅ Multiple child block yields +- ✅ Data binding between form fields and confirmation display +- ✅ Phone verification modal workflow +- ✅ Google Tag Manager event tracking +- ✅ Existing vehicle lookup and selection + +This block is a **stress test** for the migration strategy - if we can migrate Block 122 to Rails, we can migrate anything. + +--- + +## Part 1: Block 122 Architecture Analysis + +### Database Record + +``` +ID: 122 +Name: Tuffy Appt Main +Tag: (empty) +Role: (empty) +Account ID: 11187 (Account-specific - not universal) +Parent ID: NULL (top-level block) +Content Length: 43,762 bytes (~44 KB) +``` + +### Key Statistics + +- **Total Lines of Code**: ~1,150 lines (mixed HTML/JavaScript) +- **JavaScript Lines**: ~600 lines (inline, not external file) +- **HTML Structure**: Bootstrap grid-based 4-step wizard +- **Form Steps**: + 1. Service selection with service categories + 2. Date/time picker with calendar UI + 3. Customer information with vehicle selection + 4. Confirmation review +- **Form Fields**: 25+ input fields across all steps +- **Conditional Renders**: 12+ places using `{{ }}` handlebars syntax +- **Event Handlers**: 10+ JavaScript event listeners +- **API Endpoints**: Calls to `/verify_phone_number`, `/appointment` (form submit) + +--- + +## Part 2: Detailed Structure Breakdown + +### Data Dependencies + +**Account-Level Properties Accessed**: +```javascript +// Via {{account.property}} syntax +account.id // Account ID +account.name // Display name +account.address_str // Address +account.phone_number // Phone +account.online_scheduling_promo_message // Promotional message +account.require_addr_for_appt // Address field requirement +account.require_costco_membership_number? // Costco membership requirement +account.show_towing // Towing field requirement +account.require_license_plate // License plate requirement +account.use_mobile_verification // Phone verification toggle +account.store_hours_options // Hour dropdowns (HTML options) +account.store_hours_html // Rendered hours display +account.render_new_appt_times_string // Calendar filtering flag +account.appt_min_hours // Minimum hours before appointment +``` + +**Page Object**: +```javascript +page.intro_or_default_text // Customizable intro copy +``` + +**Model-Level Properties**: +```javascript +account.visible_service_categories // yield: Service category loop +account.account_services // yield: Services within category +``` + +### Form Structure + +``` +Appointment Form (id="appt_form") +├── Step 1: Service Selection +│ ├── {{yield account.visible_service_categories}} → Loop through categories +│ ├── Wait/Drop-off dropdown +│ └── Submit: "Select date/time" +│ +├── Step 2: Date & Time +│ ├── jQuery UI Datepicker calendar +│ ├── Phone Verification Section (conditional on use_mobile_verification) +│ ├── Preferred Time dropdown ({{account.store_hours_options}}) +│ ├── Towing dropdown (conditional on account.show_towing) +│ └── Submit: "Enter your information" +│ +├── Step 3: Customer Information +│ ├── Personal Info (first name, last name, email, phone) +│ ├── Costco Membership (conditional on require_costco_membership_number) +│ ├── {{yield Address Fields if account.require_addr_for_appt}} +│ ├── Vehicle Selection +│ │ ├── Existing Vehicles List (populated by JavaScript) +│ │ └── {{yield New Veh Form}} (for new vehicles) +│ ├── Car Issues textarea +│ └── Submit: "Confirm your Appointment Request" +│ +├── Step 4: Confirmation +│ ├── Summary Review +│ │ ├── Customer Name & Vehicle +│ │ ├── Location ({{account.name}}, {{account.address_str}}) +│ │ ├── Services +│ │ ├── Date/Time +│ │ └── Notes +│ ├── {{yield TuffySignUps}} (right sidebar) +│ ├── Recaptcha box +│ └── Submit: "Request Appointment" +│ +└── Modal: Phone Verification + ├── Input: Verification code + ├── Actions: Verify Code, Resend Code + └── Status messages +``` + +### JavaScript Functions (Critical to Migration) + +```javascript +window.choose_step(stepNum) + // Navigate between form steps + // Updates UI, scrolls to top, shows/hides sidebars + // Core: $('.form-step').hide(); $(`#form-step-${i}`).show(); + +window.validate_step(stepNum) + // Complex validation logic including: + // - Required field checking + // - Special vehicle selection logic (existing vs new) + // - Date validation (must be 24+ hours from now) + // - Store hours validation + // - Phone verification check (if enabled) + // - Google Tag Manager event firing + +window.set_confirm() + // Updates confirmation display when fields change + // Binds to: input[name="services[]"], input[name="customer[*]"] + +window.updateConfirmationStep() + // Updates step 4 confirmation with selected vehicle + +window.verifyPhoneNumber(accountId, number) + // AJAX call to /verify_phone_number + // Shows modal with verification code input + // Expiry: 5 minutes + +window.updateTimeSlots(dateStr, accountId, wait, selectedServiceIds) + // AJAX: Fetches available time slots + // Called when date selected IF render_new_appt_times_string == true + +window.change_location() + // Reopens location selection + +window.validate_store_hours() + // Checks if appointment time is within store hours + // Uses window.active_hours array + +window.removeSelectError / removeError + // Event handlers for error removal on user input +``` + +### Child Block Yields + +Block 122 references these child blocks via `{{yield ...}}`: + +1. **{{yield account.visible_service_categories}}** + - Loop through service categories + - Each category has checkboxes for services + - Calls `{{yield account.account_services}}` for services in category + - Example: `yield Block 25342` (service selector) + +2. **{{yield Address Fields if account.require_addr_for_appt}}** + - Conditional address input block + - Only shown if account.require_addr_for_appt is true + +3. **{{yield New Veh Form}}** + - New vehicle year/make/model selectors + - Example: block 24794, 24793 (vehicle form blocks) + +4. **{{yield TuffySignUps}}** + - Newsletter signup on confirmation step + - Example: block 2418 (empty placeholder) + +--- + +## Part 3: Complexity Metrics + +### Why Block 122 is High Complexity + +| Factor | Score | Details | +|--------|-------|---------| +| **Size** | 9/10 | 43KB, ~1,150 lines | +| **JavaScript** | 10/10 | 600+ lines of inline JS, event handlers, AJAX | +| **Conditional Logic** | 7/10 | 12+ places with `if` conditions, feature flags | +| **State Management** | 9/10 | Multi-step wizard with complex validation | +| **Data Binding** | 8/10 | Form fields must sync with confirmation display | +| **External Dependencies** | 8/10 | jQuery UI Datepicker, reCAPTCHA, Google Tag Manager | +| **API Calls** | 6/10 | POST /appointment, GET /verify_phone_number, XHR for time slots | +| **Testing Difficulty** | 9/10 | Integration + E2E testing required for full coverage | + +**Overall Complexity**: **8.5/10** - Enterprise-grade appointment form with sophisticated UX + +--- + +## Part 4: Migration Strategy + +### Approach: Decompose into Rails Components + +Instead of replacing Block 122 directly, decompose into: + +1. **Views** - Multi-step form partials +2. **Controller** - Step navigation, validation +3. **Service** - Business logic (validation, time slot fetching) +4. **Presenter** - Data preparation for views +5. **JavaScript** - Extract to external JS files + +### File Structure + +``` +app/views/appointments/ +├── _appointment_form.html.erb # Main form wrapper +├── _step_1_services.html.erb # Service selection +├── _step_2_datetime.html.erb # Date/time picker +├── _step_3_customer_info.html.erb # Personal info & vehicle +├── _step_4_confirmation.html.erb # Review & submit +└── _phone_verification_modal.html.erb # Phone verification modal + +app/controllers/ +└── appointments_controller.rb # Step navigation, submission + +app/presenters/ +└── appointment_form_presenter.rb # Data preparation, conditionals + +app/services/ +└── validate_appointment_service.rb # Complex validation logic +└── fetch_appointment_slots_service.rb # Calendar/slot fetching + +app/assets/javascripts/ +└── appointment_form.js # Extract inline JS here +``` + +### Step 1: Create Presenter + +File: `app/presenters/appointment_form_presenter.rb` + +```ruby +class AppointmentFormPresenter + def initialize(account, appointment = nil) + @account = account + @appointment = appointment || Appointment.new + end + + # Account properties + def account_id; @account.id; end + def account_name; @account.name; end + def account_address; @account.address_str; end + def account_phone; @account.phone_number; end + + # Conditionals + def show_online_scheduling_promo? + @account.online_scheduling_promo_message.present? + end + + def online_scheduling_promo_message + @account.online_scheduling_promo_message + end + + def require_address? + @account.require_addr_for_appt? + end + + def require_costco_membership? + @account.require_costco_membership_number? + end + + def show_towing? + @account.show_towing? + end + + def use_mobile_verification? + @account.use_mobile_verification? + end + + def minimum_appointment_hours + @account.appt_min_hours || 24 + end + + def filter_time_slots? + @account.render_new_appt_times_string == 'true' + end + + # Service categories (for yield loop) + def visible_service_categories + @account.visible_service_categories + end + + # Store hours + def store_hours_html + @account.store_hours_html + end + + def store_hours_options + @account.store_hours_options + end + + def store_hours_json + # For JavaScript time validation + @account.store_hours_for_js.to_json + end + + # Intro text + def intro_text + Page.intro_or_default_text || "Schedule your appointment with us." + end +end +``` + +### Step 2: Create Controller + +File: `app/controllers/appointments_controller.rb` + +```ruby +class AppointmentsController < ApplicationController + before_action :set_account + before_action :set_presenter, only: [:new] + + # Show appointment form + def new + @appointment = Appointment.new + @presenter = AppointmentFormPresenter.new(@account) + + # For existing vehicles (pre-fill if customer found) + @existing_vehicles = @account.vehicles.where( + phone_number: params[:phone] + ) if params[:phone].present? + end + + # Handle step navigation (AJAX) + def validate_step + step = params[:step].to_i + data = JSON.parse(params[:form_data] || '{}') + + result = ValidateAppointmentService.perform( + account: @account, + step: step, + data: data + ) + + if result[:valid] + render json: { valid: true } + else + render json: { valid: false, errors: result[:errors] }, status: :unprocessable_entity + end + end + + # Fetch available time slots + def available_slots + date = params[:date] + services = params[:service_ids] || [] + wait_type = params[:wait] + + slots = FetchAppointmentSlotsService.perform( + account: @account, + date: date, + service_ids: services, + wait_type: wait_type + ) + + render json: { slots: slots } + end + + # Verify phone number for existing customer lookup + def verify_phone + phone = params[:phone] + result = VerifyPhoneService.perform(phone: phone) + + if result[:valid] + # Send verification code (SMS/email) + render json: { + valid: true, + verification_code: result[:code], # For testing only + expires_at: 5.minutes.from_now + } + else + render json: { valid: false }, status: :unprocessable_entity + end + end + + # Create appointment + def create + service = CreateAppointmentService.perform( + account: @account, + customer_params: customer_params, + appointment_params: appointment_params + ) + + if service.is_a?(Appointment) + # Send confirmation email, etc. + render json: { success: true, appointment_id: service.id } + else + render json: { success: false, error: service }, status: :unprocessable_entity + end + end + + private + + def set_account + @account = current_account + end + + def set_presenter + @presenter = AppointmentFormPresenter.new(@account) + end + + def customer_params + params.require(:customer).permit( + :first_name, :last_name, :email, :phone, + :costco_membership_number + ) + end + + def appointment_params + params.require(:appointment).permit( + :appointment_date, :appointment_time, :wait, + :towing, :car_issues, :car_year, :car_make, :car_model, + :service_ids => [] + ) + end +end +``` + +### Step 3: Create Views + +#### File: `app/views/appointments/new.html.erb` + +```erb +<%= render 'appointment_form', presenter: @presenter, existing_vehicles: @existing_vehicles %> +``` + +#### File: `app/views/appointments/_appointment_form.html.erb` + +```erb +
+
+

Schedule an Appointment

+
+ +
+ + + <%= form_with url: appointments_path, method: :post, id: 'appt_form', local: true, class: 'container' do |f| %> + + + + + + + +
+ +
+ +
+
    +
  • Service(s)
  • +
  • Date/Time
  • +
  • Your Info
  • +
  • Confirm
  • +
+
+ + +
+ <%= presenter.intro_text %> +
+ + + <% if presenter.show_online_scheduling_promo? %> +
+ <%= presenter.online_scheduling_promo_message %> +
+ <% end %> + + + <%= render 'step_1_services', presenter: presenter %> + + + <%= render 'step_2_datetime', presenter: presenter %> + + + <%= render 'step_3_customer_info', presenter: presenter, existing_vehicles: existing_vehicles %> + + + <%= render 'step_4_confirmation', presenter: presenter %> +
+ + + + + +
+
+

Requested Appointment Summary

+
+
+

+ <%= presenter.account_name %>
+ <%= presenter.account_address %>
+ <%= presenter.account_phone %> +

+

+ Hours of Operation
+

    + <%= raw presenter.store_hours_html %> +
+

+
+ Change +
+
+
+
+ + <% if presenter.use_mobile_verification? %> + <%= render 'phone_verification_modal' %> + <% end %> + <% end %> +
+
+ + + +``` + +#### File: `app/views/appointments/_step_1_services.html.erb` + +```erb +
+
+ + <% presenter.visible_service_categories.each do |category| %> +
+

<%= category.name %>

+
+ <% category.account_services.each do |account_service| %> + + <% end %> +
+
+ <% end %> + + +
+ + +
+
+ +
+ +
+
+``` + +#### File: `app/views/appointments/_step_2_datetime.html.erb` + +```erb + +``` + +#### File: `app/views/appointments/_step_3_customer_info.html.erb` + +```erb + +``` + +#### File: `app/views/appointments/_step_4_confirmation.html.erb` + +```erb + +``` + +### Step 4: Extract JavaScript to External File + +File: `app/assets/javascripts/appointment_form.js` + +```javascript +class AppointmentForm { + constructor(options = {}) { + this.accountId = options.accountId; + this.filterTimeSlots = options.filterTimeSlots; + this.usePhoneVerification = options.usePhoneVerification; + this.minimumHours = options.minimumHours || 24; + this.storeHours = options.storeHours || {}; + + this.currentStep = 1; + this.phoneVerified = false; + this.selectedExistingVehicle = null; + + this.init(); + } + + init() { + this.setupEventListeners(); + this.initDatepicker(); + this.setupStepNavigation(); + } + + setupEventListeners() { + // Service checkboxes + document.querySelectorAll('.service-checkbox').forEach(checkbox => { + checkbox.addEventListener('change', () => this.updateServicesSummary()); + }); + + // Form field changes + document.querySelectorAll('input[name^="customer["], select[name^="appointment["]').forEach(field => { + field.addEventListener('change', () => this.updateConfirmationDisplay(field)); + }); + + // Wait/Drop-off + document.getElementById('wait').addEventListener('change', (e) => { + document.getElementById('confirm_wait').textContent = e.target.value; + }); + + // Phone verification + if (this.usePhoneVerification) { + document.getElementById('verifyPhoneBtn').addEventListener('click', () => { + this.verifyPhone(); + }); + } + + // Existing vehicles + if (document.getElementById('existingVehiclesList').children.length > 0) { + document.querySelectorAll('input[name="vehicle_selection"]').forEach(radio => { + radio.addEventListener('change', (e) => { + this.selectExistingVehicle(e.target.value); + }); + }); + document.getElementById('addNewVehicleBtn').addEventListener('click', () => { + this.switchToNewVehicle(); + }); + } + } + + initDatepicker() { + // Initialize jQuery UI datepicker + // Implement date selection logic + } + + setupStepNavigation() { + // Setup step transitions + } + + goToStep(step) { + if (!this.validateStep(this.currentStep)) { + return false; + } + + this.currentStep = step; + this.showStep(step); + return false; + } + + showStep(step) { + // Hide all steps + document.querySelectorAll('[id^="form-step-"]').forEach(el => { + el.style.display = 'none'; + }); + + // Show selected step + document.getElementById(`form-step-${step}`).style.display = 'block'; + + // Update progress indicator + this.updateProgressIndicator(step); + + // Update sidebars + if (step === 4) { + document.getElementById('summary-sidebar').style.display = 'none'; + document.getElementById('sign-up-sidebar').style.display = 'block'; + } else { + document.getElementById('sign-up-sidebar').style.display = 'none'; + document.getElementById('summary-sidebar').style.display = 'block'; + } + + // Scroll to top + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + + validateStep(step) { + // Complex validation logic + // Returns true if valid, false otherwise + } + + updateConfirmationDisplay(field) { + // Update confirmation section as user types + } + + updateServicesSummary() { + // Update services display + } + + verifyPhone() { + // AJAX call to verify phone number + } + + selectExistingVehicle(vehicleId) { + // Handle existing vehicle selection + } + + switchToNewVehicle() { + // Switch to new vehicle form + } + + change_location() { + // Reopen location selection + } +} +``` + +--- + +## Part 5: Risks and Mitigation + +### High Risk Areas + +| Risk | Severity | Mitigation | +|------|----------|-----------| +| **Complex JavaScript Behavior** | CRITICAL | Extract to external file, comprehensive testing, unit tests for validation logic | +| **Form State Management** | HIGH | Use Stimulus controllers or React component for state | +| **AJAX Calls (time slots, phone verify)** | HIGH | Mock endpoints in tests, use fetch API instead of jQuery | +| **Date Validation** | HIGH | Use Rails date validation + JavaScript, test edge cases | +| **Existing Vehicle Lookup** | MEDIUM | Create service for vehicle lookup, test with real data | +| **Phone Verification** | MEDIUM | Create SMS service abstraction, test verification flow | +| **Google Tag Manager** | LOW | Keep GTM code separate, can be added back easily | +| **Accessibility** | MEDIUM | Add ARIA labels, test with screen readers | + +--- + +## Part 6: Implementation Timeline + +### Phase 1: Setup & Views (2 weeks) +- Create presenter class +- Create view partials +- Create controller +- Basic HTML structure + +### Phase 2: JavaScript & Validation (2 weeks) +- Extract inline JavaScript to Stimulus controller +- Implement step validation +- Implement date picker integration +- Implement form field synchronization + +### Phase 3: Services & API Integration (1.5 weeks) +- Create validation services +- Create time slot fetching service +- Create phone verification service +- Create appointment creation service + +### Phase 4: Testing & Polish (1.5 weeks) +- Unit tests for services +- Integration tests for form flow +- E2E tests with Capybara +- Performance testing +- Browser compatibility testing + +### Phase 5: Rollout (1 week) +- Feature flag configuration +- Beta testing with test account +- Gradual rollout to production +- Monitor error rates +- Collect user feedback + +**Total**: 8 weeks for complete migration + +--- + +## Part 7: Conclusion + +Block 122 represents **the highest complexity** in PISTN's Block system. Its migration path shows: + +1. **Decomposition**: Break monolithic Block into separate concerns (views, controller, services) +2. **Modernization**: Replace inline JavaScript with standard Rails patterns (Stimulus, fetch API) +3. **Testability**: Extract business logic into services that can be tested independently +4. **Maintainability**: Standard Rails conventions make code easier to understand and modify + +**Success Criteria**: +- ✅ Feature parity with original Block 122 +- ✅ 100% form submission success rate +- ✅ Phone verification flow works +- ✅ Existing vehicle lookup functional +- ✅ All validations pass +- ✅ Performance within 10% of original +- ✅ Accessibility meets WCAG 2.1 AA + +**If Block 122 can be migrated successfully, ALL blocks can be migrated.** + +--- + +**Status**: Architectural analysis complete - ready for implementation +**Next Steps**: +1. Create Stimulus controller for form state management +2. Implement form partials with real data +3. Build validation services +4. Begin E2E testing framework +