# Solution Design Document

## Validation Checklist

- [x] All required sections are complete
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] All context sources are listed with relevance ratings
- [x] Project commands are discovered from actual project files
- [x] Constraints → Strategy → Design → Implementation path is logical
- [x] Architecture pattern is clearly stated with rationale
- [x] Every component in diagram has directory mapping
- [x] Every interface has specification
- [x] Error handling covers all error types
- [x] Quality requirements are specific and measurable
- [x] Every quality requirement has test coverage
- [x] **All architecture decisions confirmed by user** (ADR-1, ADR-2, ADR-3)
- [x] Component names consistent across diagrams
- [x] A developer could implement from this design

---

## Constraints

**CON-1 Technical Platform**
- PHP 8.x with Slim 2.6.2 framework and Twig 1.44.8 templating
- MySQL multi-store architecture (central `kiosk_buykiosk` + per-store `kiosk_{typeNum}`)
- SweetAlert 1.x for modal notifications (no migration to SweetAlert2)
- Bootstrap 3 + FontAwesome for UI
- Mobile-responsive for tablet use on store floor

**CON-2 Coding Standards & Architecture**
- PSR-4 autoloading for all classes under `BuyerKiosk\` namespace
- Existing Event Management module in `userfrosting/src/BuyerKiosk/EventManagement/`
- Existing IntegrationService and 7 adapter classes must be extended, not replaced
- Database migrations via JSON migration files in `userfrosting/migrations/input/`
- Deployment via existing `./deploy.sh` pipeline

**CON-3 Security & Permissions**
- Session-based authentication for workspace endpoints
- Existing permission: `uri_events_manage` for event CRUD operations
- Store isolation via `checkStoreGroup($typeNum)` pattern
- CSRF protection on all form submissions

**CON-4 Integration Constraints**
- Must not break existing 7 integration adapters (Comeback Cash, Signage, SMS Blast, SMS Trigger, Tasks, Notes, Backstock)
- Must maintain existing partial success pattern (event creation succeeds even if integrations fail)
- Must preserve existing `event_integrations` table schema
- Hub and Spoke UI enhances Step 3 without replacing wizard structure

## Implementation Context

**IMPORTANT**: You MUST read and analyze ALL listed context sources to understand constraints, patterns, and existing architecture.

### Required Context Sources

- ICO-1 Event Management Core (CRITICAL)
```yaml
- file: userfrosting/src/BuyerKiosk/EventManagement/Services/IntegrationService.php
  relevance: CRITICAL
  why: "Orchestrates all integration adapters; returns {created, failed} arrays"

- file: userfrosting/src/BuyerKiosk/EventManagement/Controllers/EventApiController.php
  relevance: HIGH
  sections: [lines 251-325 create(), lines 1209-1286 integration transformation]
  why: "Event creation API that needs enhanced response format"

- file: userfrosting/src/BuyerKiosk/EventManagement/Models/EventIntegration.php
  relevance: HIGH
  why: "7 TYPE constants, 4 STATUS constants, config JSON structure"

- file: userfrosting/templates/themes/default/admin/event-management/create.html
  relevance: HIGH
  sections: [lines 1047-1203 Step 3 integrations]
  why: "Current wizard Step 3 - will be enhanced with Hub and Spoke UI"
```

- ICO-2 Integration Adapters
```yaml
- file: userfrosting/src/BuyerKiosk/EventManagement/Adapters/IntegrationAdapterInterface.php
  relevance: HIGH
  why: "Interface contract: create, validateConfig, getDefaultConfig, getStatus"

- file: userfrosting/src/BuyerKiosk/EventManagement/Adapters/ComebackCashAdapter.php
  relevance: HIGH
  why: "Most complex adapter: side, earningType, couponValue required; 15+ config fields"

- file: userfrosting/src/BuyerKiosk/EventManagement/Adapters/SignageAdapter.php
  relevance: MEDIUM
  why: "Requires slideIds OR tags; dual DB (store + global)"

- file: userfrosting/src/BuyerKiosk/EventManagement/Adapters/SmsAdapter.php
  relevance: MEDIUM
  why: "Handles both blast and trigger types via config['type']"

- file: userfrosting/src/BuyerKiosk/EventManagement/Adapters/TaskAdapter.php
  relevance: MEDIUM
  why: "taskName required; auto-creates task groups"

- file: userfrosting/src/BuyerKiosk/EventManagement/Adapters/NoteAdapter.php
  relevance: MEDIUM
  why: "title, content required; wind-down aware"

- file: userfrosting/src/BuyerKiosk/EventManagement/Adapters/BackstockAdapter.php
  relevance: MEDIUM
  why: "name required; category many-to-many linking"
```

- ICO-3 Event Detail Page (UI Patterns)
```yaml
- file: userfrosting/templates/themes/default/admin/event-management/detail.html
  relevance: HIGH
  sections: [lines 1065-1130 integration cards]
  why: "Existing status badge patterns and integration card structure"

- file: public_html/css/admin/event-management.css
  relevance: MEDIUM
  why: "Existing badge colors (pending/active/completed/failed) and card patterns"
```

- ICO-4 Related Specifications
```yaml
- doc: docs/specs/004-unified-event-management/solution-design.md
  relevance: HIGH
  sections: [Integration Adapters, IntegrationService patterns]
  why: "Parent specification - must align with existing architecture"

- doc: docs/specs/003-comeback-cash/solution-design.md
  relevance: MEDIUM
  why: "Comeback Cash adapter business rules and validation"
```

### Implementation Boundaries

- **Must Preserve**:
  - Existing wizard steps 1, 2, 4 structure and behavior
  - All 7 integration adapter classes (Comeback Cash, Signage, SMS Blast, SMS Trigger, Tasks, Notes, Backstock)
  - Existing IntegrationService orchestration patterns
  - `event_integrations` table schema (eventId, integrationType, foreignId, config, status, relativeDays)
  - Existing partial success pattern (event creation succeeds even if integrations fail)
  - Detail page integration card display patterns

- **Can Modify**:
  - `create.html` Step 3 (Integrations) - enhance with Hub and Spoke UI
  - `EventApiController::create()` - enhance response format with `integrationResults` array
  - `detail.html` integration cards - add new badge types ("New", "Setup Required")
  - `event-management.css` - add new CSS classes for Hub/Spoke and enhanced badges
  - Add new JavaScript modules for hub-spoke navigation and confirmation modal

- **Must Not Touch**:
  - Core adapter `create()`, `activate()`, `delete()` method signatures
  - Existing API endpoints consumed by mobile app
  - `event_integrations` database schema
  - Other wizard steps (1, 2, 4) beyond minor adjustments

### External Interfaces

This feature is an internal UI/UX enhancement to the event creation wizard. No new external system integrations are required.

#### System Context Diagram

```mermaid
graph TB
    subgraph "Users"
        SM[Store Manager]
    end

    subgraph "Event Management System"
        Wizard[Create Wizard Step 3]
        API[Event API]
        Modal[Confirmation Modal]
        Detail[Detail Page]
    end

    subgraph "Existing Integration Systems"
        CC[Comeback Cash]
        DS[Digital Signage]
        SMS[SMS Blasts/Triggers]
        TK[Tasks]
        NT[Notes]
        BS[Backstock]
    end

    subgraph "Data Storage"
        StoreDB[(Store DB)]
    end

    SM --> Wizard
    Wizard --> API
    API --> CC
    API --> DS
    API --> SMS
    API --> TK
    API --> NT
    API --> BS
    API --> Modal
    Modal --> Detail
    API --> StoreDB
```

#### Interface Specifications

```yaml
# Inbound Interfaces (what calls this system)
inbound:
  - name: "Admin Web Interface"
    type: HTTPS
    format: HTML + REST JSON
    authentication: Session (UserFrosting)
    data_flow: "Event wizard interactions, integration configuration"
    permissions: uri_events_manage

# Internal Interfaces (existing adapters called by IntegrationService)
internal:
  - name: "ComebackCashAdapter"
    data_flow: "Creates ccEvents with earning/redemption config"
    target_table: ccEvents

  - name: "SignageAdapter"
    data_flow: "Schedules slides in dsLoop"
    target_table: dsLoop, digitalSignSchedule

  - name: "SmsAdapter"
    data_flow: "Creates SMS blasts or triggers"
    target_table: seller_marketing_blasts, seller_marketing_triggers

  - name: "TaskAdapter"
    data_flow: "Creates event-linked tasks"
    target_table: tasks, taskGroups

  - name: "NoteAdapter"
    data_flow: "Creates event-linked notes"
    target_table: workbook_notes

  - name: "BackstockAdapter"
    data_flow: "Creates bsEvents with category links"
    target_table: bsEvents, bsEvent_Categories

# Data Interfaces
data:
  - name: "Store Database"
    type: MySQL
    connection: PDO via dbConnectByName($store->getDbName())
    data_flow: "Event and integration persistence"
    tables: [events, event_integrations, plus adapter target tables]
```

### Cross-Component Boundaries

This feature touches UI and API within a single codebase. No cross-team coordination required.

- **API Contracts**: Enhanced `POST /api/:typeNum/events` response is additive (new `integrationResults` field) - no breaking changes
- **Shared Resources**: Store database (`kiosk_{typeNum}`) - uses existing tables
- **Breaking Change Policy**: N/A - all changes are additive enhancements

### Project Commands

```bash
# Component: BuyerKiosk Web Application
Location: /Users/rvanvuren/Projects/buyerkiosk-web

## Environment Setup
Install Dependencies: cd userfrosting && composer install
Configuration: userfrosting/config/config.php

## Testing Commands
All Tests: ./test.sh
Unit Tests: ./test.sh --testsuite unit
Integration Tests: ./test.sh --testsuite integration
Test Coverage: ./test.sh --coverage

## Database Operations
Run Migrations: php userfrosting/conductor migrate
Create Migration: Create JSON file in userfrosting/migrations/input/

## Deployment
Deploy: ./deploy.sh  # Runs tests then deploys

## Development Server
Local Environment: Apache via MAMP (dev2.buyerkiosk.com)

## Feature-Specific Verification
After implementation:
1. Create event via wizard: /admin/{typeNum}/events/create
2. Enable integrations in Step 3, configure each
3. Complete wizard - verify confirmation modal appears
4. Check event detail page: /admin/{typeNum}/events/{eventId}
5. Verify integration status badges display correctly
```

## Solution Strategy

- **Architecture Pattern**: Enhancement to existing MVC structure
  - Enhance existing Step 3 UI with Hub and Spoke navigation pattern
  - Extend EventApiController response format
  - Add new JavaScript module for confirmation modal
  - Add CSS for new UI components

- **Integration Approach**:
  - **UI Layer**: Replace Step 3 toggle-based UI with Hub (integration cards) and Spoke (config modals) pattern
  - **API Layer**: Enhance `POST /api/:typeNum/events` response to include `integrationResults` array with per-integration status
  - **Frontend JS**: New confirmation modal using SweetAlert 1.x, displaying success/failure per integration
  - **Validation**: Leverage existing adapter `validateConfig()` methods for inline validation

- **Justification**:
  - Hub and Spoke keeps Step 3 familiar (cards) while enabling full configuration (spokes)
  - Using existing IntegrationService partial success pattern ensures reliability
  - SweetAlert 1.x confirmation modal maintains UI consistency
  - No database schema changes required - uses existing `event_integrations` structure

- **Key Decisions**:
  1. **Hub and Spoke over inline expansion**: Cleaner UX for complex config forms; prevents page clutter
  2. **Modal per integration**: Each spoke opens full config form without leaving Step 3
  3. **Progressive disclosure**: Required fields visible first; "Advanced Options" expandable
  4. **Draft vs Schedule validation**: Drafts allow incomplete config; scheduling requires all enabled integrations configured
  5. **Retry in confirmation modal**: Failed integrations can be retried up to 3 times before "Configure Later"

## Building Block View

### Components

```mermaid
graph TB
    subgraph "Presentation Layer - Step 3 Enhancement"
        Hub[Integration Hub<br/>Card Grid]
        Spoke[Config Spokes<br/>Modal Forms]
        Modal[Confirmation Modal<br/>Post-Creation]
    end

    subgraph "Presentation Layer - Detail Page"
        Cards[Integration Cards]
        Badges[Status Badges]
    end

    subgraph "API Layer"
        EventAPI[EventApiController]
    end

    subgraph "Business Logic Layer"
        IntSvc[IntegrationService]
        Adapters[7 Integration Adapters]
    end

    subgraph "Data Layer"
        Events[(events)]
        Integrations[(event_integrations)]
        Targets[(ccEvents, dsLoop,<br/>tasks, notes, etc.)]
    end

    Hub --> Spoke
    Spoke --> EventAPI
    EventAPI --> IntSvc
    IntSvc --> Adapters
    Adapters --> Targets
    EventAPI --> Events
    EventAPI --> Integrations
    EventAPI --> Modal
    Modal --> Cards
    Cards --> Badges
```

### Directory Map

**Backend (API Enhancement)**
```
userfrosting/src/BuyerKiosk/EventManagement/
├── Controllers/
│   └── EventApiController.php           # MODIFY: Enhance create() response with integrationResults
```

**Frontend Templates**
```
userfrosting/templates/themes/default/admin/event-management/
├── create.html                           # MODIFY: Step 3 Hub and Spoke UI
├── detail.html                           # MODIFY: Enhanced status badges
└── partials/
    ├── integration-hub.html              # NEW: Hub view with integration cards
    ├── integration-spokes/               # NEW: Config spoke templates
    │   ├── comeback-cash-config.html     # NEW: Comeback Cash config form
    │   ├── signage-config.html           # NEW: Signage config form
    │   ├── sms-blast-config.html         # NEW: SMS Blast config form
    │   ├── sms-trigger-config.html       # NEW: SMS Trigger config form
    │   ├── task-config.html              # NEW: Task config form
    │   ├── note-config.html              # NEW: Note config form
    │   └── backstock-config.html         # NEW: Backstock config form
    └── confirmation-modal.html           # NEW: Post-creation confirmation modal
```

**Frontend JavaScript**
```
public_html/js/admin/event-management/
├── event-form.js                         # MODIFY: Add hub-spoke navigation logic
├── integration-hub.js                    # NEW: Hub and spoke controller
├── integration-configs/                  # NEW: Per-integration config handlers
│   ├── comeback-cash-config.js           # NEW: CC form validation
│   ├── signage-config.js                 # NEW: Signage form logic
│   ├── sms-config.js                     # NEW: SMS blast/trigger forms
│   ├── task-config.js                    # NEW: Task form logic
│   ├── note-config.js                    # NEW: Note form logic
│   └── backstock-config.js               # NEW: Backstock form logic
└── confirmation-modal.js                 # NEW: Confirmation modal controller
```

**Frontend CSS**
```
public_html/css/admin/
├── event-management.css                  # MODIFY: Hub/spoke styles, new badges
└── event-management/
    └── integration-config.css            # NEW: Spoke modal and form styles
```

### Interface Specifications

#### Interface Documentation References

```yaml
interfaces:
  - name: "Event API Pattern"
    doc: @docs/specs/004-unified-event-management/solution-design.md
    relevance: HIGH
    sections: [Event CRUD API, Integration Management API]
    why: "Must follow existing API patterns and enhance, not replace"

  - name: "Integration Adapter Interface"
    doc: @userfrosting/src/BuyerKiosk/EventManagement/Adapters/IntegrationAdapterInterface.php
    relevance: HIGH
    why: "Adapter validateConfig() used for inline form validation"
```

#### Data Storage Changes

**No database schema changes required.** This feature uses existing tables:

```yaml
Existing Tables Used:
  - events: Event records (no changes)
  - event_integrations: Integration links with config JSON (no changes)
  - ccEvents, dsLoop, tasks, workbook_notes, bsEvents: Target tables (no changes)

Note: The integrationResults in API response is computed at runtime, not stored.
```

#### Internal API Changes

**Enhanced Create Event Response** (Feature 2 from PRD)

```yaml
Endpoint: Create Event (Enhanced Response)
  Method: POST
  Path: /api/:typeNum/events
  Auth: Session + uri_events_manage permission

  Request: (unchanged from existing)
    name: string (required)
    eventType: string (required)
    startDate: date (required)
    endDate: date (required)
    integrations: object (enabled integrations with config)

  Response (ENHANCED):
    success: true
    event: Event object
    # NEW: Per-integration results
    integrationResults:
      - type: "comeback_cash"
        status: "created" | "failed" | "skipped"
        foreignId: int | null
        error: string | null
      - type: "signage"
        status: "created"
        foreignId: 456
        error: null
      # ... one entry per enabled integration
    # NEW: Summary counts
    summary:
      total_enabled: int
      created: int
      failed: int
      skipped: int

  Error Response:
    success: false
    error: string
    code: "VALIDATION_ERROR" | "PERMISSION_DENIED" | "DATABASE_ERROR"
```

**Retry Integration Endpoint** (Feature 6 from PRD - Should Have)

```yaml
Endpoint: Retry Failed Integration
  Method: POST
  Path: /api/:typeNum/events/:eventId/integrations/:type/retry
  Auth: Session + uri_events_manage permission

  Request:
    config: object (optional - use existing config if not provided)

  Response:
    success: true
    integration:
      type: string
      status: "created" | "failed"
      foreignId: int | null
      error: string | null
    retryCount: int (1-3)

  Business Rules:
    - Maximum 3 retry attempts per integration per event creation session
    - After 3 failures, show "Configure Manually" option
```

#### Application Data Models

**No new models.** This feature uses existing models with no modifications:

```yaml
Existing Models Used:
  - EventIntegration: Links event to integration with config JSON
    - Properties: id, eventId, integrationType, foreignId, config, status, relativeDays
    - Used as-is for storing integration configuration

  - IntegrationResult (NEW - Runtime only, not persisted):
    - type: string (integration type)
    - status: "created" | "failed" | "skipped"
    - foreignId: int | null
    - error: string | null
    - Purpose: Returned in API response for confirmation modal
```

#### Integration Points

**Internal Integration (UI ↔ API ↔ Adapters)**

```yaml
# UI to API Communication
- from: Step 3 Hub/Spoke UI
  to: EventApiController
  protocol: REST JSON (AJAX)
  data_flow: "Integration configuration collected from spoke forms"

- from: Confirmation Modal
  to: EventApiController (retry endpoint)
  protocol: REST JSON (AJAX)
  data_flow: "Retry requests for failed integrations"

# API to Adapter Communication
- from: EventApiController
  to: IntegrationService
  to: Integration Adapters (7 types)
  protocol: PHP method calls
  data_flow: "Config passed to adapter.create(), results collected"
```

**No external third-party integrations added.** All integration targets (Comeback Cash, Signage, SMS, etc.) are internal systems already integrated.

### Implementation Examples

#### Example: Enhanced API Response with Integration Results

**Why this example**: Shows how EventApiController should be modified to return per-integration results.

```php
// EventApiController.php - Enhanced create() method
public function create($app, $typeNum)
{
    // ... existing validation and event creation ...

    $event = $this->eventService->create($eventData);

    // Process integrations and collect results
    $integrationResults = [];
    $summary = ['total_enabled' => 0, 'created' => 0, 'failed' => 0, 'skipped' => 0];

    if (!empty($request['integrations'])) {
        foreach ($request['integrations'] as $type => $config) {
            if (empty($config['enabled'])) {
                continue;
            }

            $summary['total_enabled']++;

            try {
                $foreignId = $this->integrationService->createIntegration(
                    $event,
                    $this->mapIntegrationType($type),
                    $config
                );

                $integrationResults[] = [
                    'type' => $this->mapIntegrationType($type),
                    'status' => 'created',
                    'foreignId' => $foreignId,
                    'error' => null
                ];
                $summary['created']++;

            } catch (IntegrationException $e) {
                $integrationResults[] = [
                    'type' => $e->getIntegrationType(),
                    'status' => 'failed',
                    'foreignId' => null,
                    'error' => $e->getMessage()
                ];
                $summary['failed']++;
            }
        }
    }

    echo json_encode([
        'success' => true,
        'event' => $event->toArray(),
        'integrationResults' => $integrationResults,  // NEW
        'summary' => $summary  // NEW
    ]);
}
```

#### Example: Hub and Spoke JavaScript Controller

**Why this example**: Shows the navigation pattern between hub view and spoke config modals.

```javascript
// integration-hub.js - Hub and Spoke navigation
const IntegrationHub = {
    currentSpoke: null,
    configStates: {},  // Stores config state per integration type

    init: function() {
        // Bind click handlers on integration cards
        document.querySelectorAll('.integration-card').forEach(card => {
            card.addEventListener('click', (e) => {
                if (!card.classList.contains('disabled')) {
                    this.openSpoke(card.dataset.type);
                }
            });
        });
    },

    openSpoke: function(integrationType) {
        this.currentSpoke = integrationType;
        const modal = document.getElementById('spoke-modal');
        const content = document.getElementById('spoke-content');

        // Load spoke template via AJAX or from preloaded templates
        content.innerHTML = this.getSpokeTemplate(integrationType);

        // Pre-fill with existing config if available
        if (this.configStates[integrationType]) {
            this.populateForm(integrationType, this.configStates[integrationType]);
        }

        modal.classList.add('active');
        this.bindSpokeValidation(integrationType);
    },

    saveAndReturn: function() {
        const type = this.currentSpoke;
        const form = document.getElementById(`${type}-config-form`);

        // Validate via adapter rules
        const errors = this.validateConfig(type, this.collectFormData(form));

        if (errors.length > 0) {
            this.showErrors(errors);
            return;
        }

        // Save config state and update hub card
        this.configStates[type] = this.collectFormData(form);
        this.updateHubCard(type, 'configured');
        this.closeSpoke();
    },

    updateHubCard: function(type, status) {
        const card = document.querySelector(`.integration-card[data-type="${type}"]`);
        const badge = card.querySelector('.config-status');

        badge.textContent = status === 'configured' ? 'Configured ✓' : 'Not Configured';
        badge.className = `config-status ${status}`;
    }
};
```

#### Example: Confirmation Modal Display

**Why this example**: Shows how to render the post-creation confirmation modal using SweetAlert 1.x.

```javascript
// confirmation-modal.js - Post-creation confirmation
function showConfirmationModal(response) {
    const event = response.event;
    const results = response.integrationResults;
    const summary = response.summary;

    // Build HTML content for modal
    let content = `<div class="confirmation-modal">
        <h3>${event.name}</h3>
        <p class="status-summary">
            ${summary.created} of ${summary.total_enabled} integrations created
        </p>
        <ul class="integration-results">`;

    results.forEach(result => {
        const icon = result.status === 'created' ? '✓' : '✗';
        const statusClass = result.status === 'created' ? 'success' : 'failed';

        content += `<li class="${statusClass}">
            <span class="icon">${icon}</span>
            <span class="type">${formatTypeName(result.type)}</span>
            ${result.error ? `<span class="error">${result.error}</span>` : ''}
            ${result.status === 'failed' ?
                `<button class="retry-btn" data-type="${result.type}">Retry</button>` : ''}
        </li>`;
    });

    content += '</ul></div>';

    swal({
        title: summary.failed > 0 ? 'Event Created with Warnings' : 'Event Created!',
        text: content,
        html: true,
        showCancelButton: true,
        confirmButtonText: 'View Event',
        cancelButtonText: 'Create Another',
        closeOnConfirm: false
    }, function(isConfirm) {
        if (isConfirm) {
            window.location.href = `/admin/${typeNum}/events/${event.id}`;
        } else {
            window.location.href = `/admin/${typeNum}/events/create`;
        }
    });

    // Bind retry buttons
    document.querySelectorAll('.retry-btn').forEach(btn => {
        btn.addEventListener('click', () => retryIntegration(event.id, btn.dataset.type));
    });
}
```

## Runtime View

### Primary Flow: Event Creation with Integration Confirmation

#### Steps
1. User views Step 3 integration hub with 7 integration cards
2. User clicks card to open spoke config modal
3. User fills configuration form; validation runs inline
4. User saves and returns to hub; card shows "Configured ✓"
5. User repeats for each desired integration
6. User clicks "Create Event"
7. API creates event, then iterates through enabled integrations
8. Each integration result (success/failure) collected
9. Confirmation modal displays with per-integration status
10. User clicks "View Event" to see detail page

```mermaid
sequenceDiagram
    participant User
    participant Hub as Step 3 Hub
    participant Spoke as Config Modal
    participant API as EventApiController
    participant IntSvc as IntegrationService
    participant Adapters as Adapters
    participant Modal as Confirmation Modal

    User->>Hub: Views integration cards
    User->>Hub: Clicks integration card
    Hub->>Spoke: Opens config modal
    User->>Spoke: Fills configuration
    Spoke->>Spoke: Validates (adapter rules)
    Spoke-->>Hub: Returns to hub (config saved)
    Hub->>Hub: Updates card badge

    Note over User,Hub: Repeat for each integration

    User->>Hub: Clicks "Create Event"
    Hub->>API: POST /api/:typeNum/events

    loop For each enabled integration
        API->>IntSvc: createIntegration()
        IntSvc->>Adapters: adapter.create()
        Adapters-->>IntSvc: foreignId or exception
        IntSvc-->>API: Result
    end

    API->>API: Build integrationResults
    API-->>Modal: Response with results
    Modal->>User: Shows confirmation
    User->>Modal: Clicks "View Event"
```

### Error Handling

**Invalid Configuration (Spoke Form)**
- Detection: JavaScript validation or adapter.validateConfig() returns errors
- User Experience: Inline error messages on fields; Save button disabled
- Recovery: User corrects fields and validation re-runs

**Integration Creation Failure**
- Detection: Adapter throws IntegrationException
- User Experience: Integration shown with ✗ and error message in confirmation modal
- Recovery: Retry button (up to 3 attempts); after 3 failures, "Configure Manually" link

**Database Error**
- Detection: PDOException during insert
- User Experience: "Error creating event" error message
- Recovery: User refreshes and retries; transaction rolled back

**Comeback Cash Conflict**
- Detection: ComebackCashAdapter finds overlapping active event
- User Experience: Specific conflict error in confirmation modal
- Recovery: User must resolve conflict in CC module first

### Retry Flow

When a user clicks Retry for a failed integration:

1. Modal sends `POST /api/:typeNum/events/:eventId/integrations/:type/retry`
2. API attempts integration creation again (increments retryCount)
3. If success: Update modal row to show ✓
4. If failure (retryCount < 3): Show error, keep Retry enabled
5. If failure (retryCount >= 3): Disable Retry, show "Configure Manually" link to detail page

## Deployment View

**No change to existing deployment.** This feature is a UI/UX enhancement deployed via the standard `./deploy.sh` pipeline.

- **Environment**: Same Apache/PHP server environment
- **Configuration**: No new environment variables required
- **Dependencies**: No new external services
- **Performance**: No additional load; same event creation volume
- **Rollback**: Standard git revert if issues arise

## Cross-Cutting Concepts

### Pattern Documentation

```yaml
# Existing patterns used
- pattern: Integration Adapter Pattern
  relevance: CRITICAL
  why: "All 7 integration adapters follow this pattern; UI config forms must match adapter validation rules"

- pattern: Partial Success Pattern
  relevance: HIGH
  why: "Event creation succeeds even if integrations fail; must maintain this behavior"

- pattern: Hub and Spoke Navigation (NEW)
  relevance: HIGH
  why: "New UI pattern for Step 3 - central hub with modal spokes for each integration config"
```

### System-Wide Patterns Applied

- **Security**: Existing session auth and `uri_events_manage` permission; no changes
- **Error Handling**: IntegrationException for adapter failures; user-friendly messages in modal
- **Logging**: Integration failures logged with type, operation, context
- **i18n**: English only (existing pattern); no localization required

### Implementation Patterns

#### Hub and Spoke Navigation Pattern

The Step 3 integration configuration uses a hub and spoke navigation pattern:

```pseudocode
HUB VIEW:
  - Display 7 integration cards in grid
  - Each card shows: icon, name, enable toggle, config status badge
  - Click card → open spoke modal

SPOKE MODAL:
  - Full configuration form for selected integration
  - Required fields visible; "Advanced Options" collapsible
  - Real-time validation (adapter.validateConfig rules)
  - Save → return to hub with "Configured ✓" badge
  - Cancel → return to hub without saving

NAVIGATION:
  - Hub ↔ Spoke transitions are modal overlays (no page navigation)
  - ESC key closes spoke (with save prompt if dirty)
  - Multiple spokes can be configured before proceeding
```

#### Integration Result Collection Pattern

```pseudocode
FUNCTION: createEventWithIntegrations(eventData, integrations)
  results = []

  event = eventService.create(eventData)

  FOR EACH integration IN enabledIntegrations:
    TRY:
      foreignId = integrationService.createIntegration(event, integration.type, integration.config)
      results.ADD({type, status: "created", foreignId, error: null})
    CATCH IntegrationException as e:
      results.ADD({type, status: "failed", foreignId: null, error: e.getMessage()})

  RETURN {event, integrationResults: results, summary: countResults(results)}
```

## Architecture Decisions

- [x] **ADR-1 Hub and Spoke over Inline Expansion**
  - Choice: Use modal-based spoke configuration instead of expanding sections inline
  - Rationale: Complex config forms (especially Comeback Cash with 15+ fields) would clutter Step 3
  - Trade-offs: Extra click to configure; but cleaner UI and progressive disclosure
  - Status: Confirmed in PRD

- [x] **ADR-2 SweetAlert 1.x for Confirmation Modal**
  - Choice: Use existing SweetAlert 1.x library with HTML content
  - Rationale: Already loaded in codebase; consistent with other confirmations
  - Trade-offs: Limited styling compared to SweetAlert2; but no migration overhead
  - Status: Confirmed - follows existing patterns

- [x] **ADR-3 Partial Success Pattern Maintained**
  - Choice: Event creation succeeds even if integrations fail
  - Rationale: Matches existing IntegrationService behavior; users can fix integrations later
  - Trade-offs: Users may create events with incomplete integrations
  - Status: Confirmed - must maintain existing behavior

## Quality Requirements

**Performance**
- Spoke modal opens in < 200ms (local template rendering)
- Event creation API responds in < 3 seconds including all integrations
- Confirmation modal displays immediately after API response

**Usability**
- Hub cards clearly indicate enabled/disabled and configured/unconfigured states
- Spoke forms have inline validation with immediate feedback
- Confirmation modal clearly distinguishes success (✓) from failure (✗)
- Retry button accessible for failed integrations
- Keyboard navigation: ESC closes spokes, Tab navigation within forms

**Security**
- Existing `uri_events_manage` permission enforced on all endpoints
- No new attack surface; uses existing session auth

**Reliability**
- Event creation never fails due to integration failure (partial success pattern)
- Retry mechanism allows up to 3 attempts per integration
- All integration failures logged with context for troubleshooting

## Risks and Technical Debt

### Implementation Gotchas

**Comeback Cash Validation Complexity**
- Buy-side restricts earning type to "flat" only (Rule 16)
- Earning period must end before redemption starts (Rule 3)
- JavaScript validation must mirror PHP adapter validation exactly

**SweetAlert 1.x HTML Limitations**
- Limited to string-based HTML content via `text` property
- Dynamic updates (retry results) require DOM manipulation after modal opens
- Modal styling constrained by SweetAlert 1.x CSS

**Form State Management**
- Spoke forms must preserve state when toggling between integrations
- Unsaved changes warning needed when closing spoke with dirty form
- Config state must survive wizard step navigation (back/forward)

## Test Specifications

### Critical Test Scenarios

**Scenario 1: Happy Path - All Integrations Succeed**
```gherkin
Given: User is on Step 3 with uri_events_manage permission
And: User has configured Comeback Cash and SMS Blast integrations
When: User clicks "Create Event"
Then: Event is created in database
And: Both integrations created with status "created"
And: Confirmation modal shows 2/2 success with green checkmarks
And: "View Event" button navigates to detail page
```

**Scenario 2: Partial Success - One Integration Fails**
```gherkin
Given: User has configured 3 integrations (CC, SMS, Signage)
And: Comeback Cash event has date conflict
When: User clicks "Create Event"
Then: Event is created in database
And: SMS and Signage show success (✓)
And: Comeback Cash shows failure (✗) with conflict error message
And: Retry button appears for Comeback Cash
```

**Scenario 3: Spoke Validation Error**
```gherkin
Given: User opens Comeback Cash spoke modal
When: User enters negative coupon value
Then: Inline error appears: "Coupon value must be positive"
And: Save button remains disabled
And: User can correct and save
```

**Scenario 4: Retry Success**
```gherkin
Given: Confirmation modal shows failed integration with Retry button
When: User clicks Retry
And: Integration creation succeeds
Then: Row updates to show ✓
And: Retry button disappears
```

**Scenario 5: Maximum Retries Exceeded**
```gherkin
Given: User has retried 3 times with failures
When: Third retry fails
Then: Retry button is disabled
And: "Configure Manually" link appears
And: Link navigates to event detail page
```

### Test Coverage Requirements

- **Unit Tests**: Adapter validation rules, API response formatting
- **Integration Tests**: EventApiController with IntegrationService mocks
- **UI Tests**: Hub-spoke navigation, form validation, modal display
- **E2E Tests**: Full wizard flow with integration creation

---

## Addendum: Detail Page Modal Wizard Configuration (Phase 4 Revision)

**ADR-4 Modal Wizard over Simple Expansion**: Replace simple expandable cards on detail page with full modal wizards that match the depth of standalone integration configuration pages. This provides a consistent, comprehensive configuration experience without navigating away from the event detail page.

### Motivation

The original Phase 4 design (status badges + expandable summary cards) was deemed insufficient. Users need the same full configuration capabilities available on standalone integration pages, accessible directly from the event detail page via modal overlays.

### Modal Wizard Architecture

#### Wizard Types by Complexity

| Integration | Wizard Type | Steps | Rationale |
|-------------|-------------|-------|-----------|
| Comeback Cash | Multi-step (4) | Side → Earning → Redemption → Review | 15+ config fields, complex validation |
| Digital Signage | Multi-step (2) | Source → Slide/Tag Selection | Requires async slide loading |
| SMS Blast | Multi-step (2) | Message → Scheduling | Template selection + timing |
| SMS Trigger | Multi-step (2) | Message → Trigger Config | Template + trigger type + period |
| Tasks | Multi-step (2) | Details → Assignment | Task info + scheduling |
| Notes | Single-step | All fields in one form | Only 6 fields, simple validation |
| Backstock | Single-step | All fields in one form | Only 5 fields, simple validation |

#### Modal Wizard Component Structure

```
public_html/js/admin/event-management/
├── integration-wizard/
│   ├── WizardModal.js              # Base modal wizard controller
│   ├── WizardStep.js               # Step navigation & validation
│   └── wizards/
│       ├── ComebackCashWizard.js   # 4-step CC wizard
│       ├── SignageWizard.js        # 2-step signage wizard
│       ├── SmsBlastWizard.js       # 2-step SMS blast wizard
│       ├── SmsTriggerWizard.js     # 2-step SMS trigger wizard
│       ├── TaskWizard.js           # 2-step task wizard
│       ├── NoteWizard.js           # Single-step note form
│       └── BackstockWizard.js      # Single-step backstock form

userfrosting/templates/themes/default/admin/event-management/
├── wizards/
│   ├── wizard-modal.html           # Base modal template
│   ├── wizard-step.html            # Step container partial
│   └── steps/
│       ├── comeback-cash/
│       │   ├── step-1-side.html
│       │   ├── step-2-earning.html
│       │   ├── step-3-redemption.html
│       │   └── step-4-review.html
│       ├── signage/
│       │   ├── step-1-source.html
│       │   └── step-2-slides.html
│       ├── sms-blast/
│       │   ├── step-1-message.html
│       │   └── step-2-schedule.html
│       ├── sms-trigger/
│       │   ├── step-1-message.html
│       │   └── step-2-trigger.html
│       ├── task/
│       │   ├── step-1-details.html
│       │   └── step-2-assignment.html
│       ├── note-form.html          # Single-step
│       └── backstock-form.html     # Single-step
```

### Wizard Modal UI Specifications

#### Modal Container
- **Size**: Large modal (max-width: 720px, max-height: 85vh)
- **Header**: Integration icon + name + step indicator (e.g., "Step 2 of 4")
- **Body**: Scrollable step content area
- **Footer**: Back | Next/Save buttons + Cancel link
- **Keyboard**: ESC closes (with unsaved changes prompt), Tab navigation, Enter submits

#### Step Indicator
```
┌─────────────────────────────────────────────────────────────┐
│ [●] Side Selection   [○] Earning Config   [○] Redemption   [○] Review │
└─────────────────────────────────────────────────────────────┘
```
- Filled circle (●) = current/completed step
- Empty circle (○) = pending step
- Click on completed steps to navigate back
- Cannot skip ahead without completing current step

#### Step Transitions
- Validate current step before proceeding
- Show inline validation errors (no alerts)
- Animate slide transition (300ms ease-out)
- Preserve data when navigating back

### Integration Configuration Field Specifications

#### 1. Comeback Cash Wizard (4 Steps)

**Step 1: Side Selection**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| side | Radio: "Buy Side" / "Sales Side" | Yes | Must select one |

*Note: Side selection affects available options in Step 2*

**Step 2: Earning Configuration**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| earningType | Radio: Flat/Percentage/Tiered | Yes | Buy-side: Flat only |
| earningFlatAmount | Currency input | If Flat | > 0 |
| earningPercentage | Percentage input | If Percentage | 0.1-100 |
| earningTiers | Tier builder | If Tiered | Min 1 tier, no overlaps |
| minPurchaseToEarn | Currency input | No | >= 0 if set |
| earningStartRelative | Days input | No | -365 to 365 |
| earningEndRelative | Days input | No | -365 to 365 |

**Step 3: Redemption Configuration**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| couponValue | Currency input | Yes | > 0 |
| redemptionStartRelative | Days input | No | Must be after earning end |
| redemptionEndRelative | Days input | No | > redemptionStart |
| redemptionMinPurchase | Currency input | No | >= 0 if set |
| maxCouponsPerCustomer | Number input | No | > 0 if set |
| allowDoubleUp | Checkbox | No | Sales-side only |
| smsNotification | Checkbox | No | Default: true |
| refundPolicy | Select: Forfeit/Refund | No | Default: Forfeit |

**Step 4: Review**
- Summary of all selected options
- Edit links back to each step
- Final validation before save

#### 2. Digital Signage Wizard (2 Steps)

**Step 1: Source Selection**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| source | Radio: Store/Corporate/Hipbone | Yes | Default: Store |

**Step 2: Slide Selection**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| slideIds | Multi-select slide picker | One of these | Load from selected source |
| tags | Tag multi-select | required | Filter slides by tags |
| displayOrder | Number input | No | >= 0 |
| duration | Seconds input | No | > 0, default: system default |

*Slide picker loads asynchronously based on source + typeNum*

#### 3. SMS Blast Wizard (2 Steps)

**Step 1: Message Selection**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| messageId | Template picker | Yes | Load from seller_marketing_messages |

*Template picker shows preview with character count*

**Step 2: Scheduling**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| relativeDays | Days from event start | Yes | -365 to 365 |
| sendTime | Time picker (HH:MM) | Yes | Default: 10:00 |
| segment | Select: All/VIP/Active30/Active90/OptedIn | No | Default: All |

#### 4. SMS Trigger Wizard (2 Steps)

**Step 1: Message Selection**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| messageId | Template picker | Yes | Load from seller_marketing_messages |

**Step 2: Trigger Configuration**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| triggerType | Select | Yes | on_purchase/on_coupon_earn/on_coupon_redeem/on_checkin/on_first_visit |
| startRelativeDays | Days input | No | -365 to 365 |
| endRelativeDays | Days input | No | -365 to 365 |

#### 5. Task Wizard (2 Steps)

**Step 1: Task Details**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| taskName | Text input | Yes | Max 255 chars |
| phase | Select: Prep/Active/Cleanup | Yes | |
| relativeDays | Days input | Yes | -365 to 365 |
| notes | Textarea | No | |

**Step 2: Assignment**
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| assignTo | Select: Creator/Manager/All/Specific | No | Default: All |
| assignToUser | User picker | If Specific | |
| priority | Select: High/Normal/Low | No | Default: Normal |
| createGroup | Checkbox | No | Default: true |

#### 6. Note Form (Single Step)

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| title | Text input | Yes | Max 255 chars |
| content | Textarea (rich text) | Yes | Non-empty |
| startRelativeDays | Days input | No | -365 to 365 |
| endRelativeDays | Days input | No | -365 to 365 or null |
| isManagerOnly | Checkbox | No | Default: false |
| isPinned | Checkbox | No | Default: false |

#### 7. Backstock Form (Single Step)

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| name | Text input | Yes | Max 100 chars |
| categories | Category multi-select | No | Load from store categories |
| markupAdjustment | Range slider (-100 to 100) | No | Default: 0 |
| priorityBoost | Checkbox | No | Default: false |

### API Endpoints

#### Update Integration Configuration
```
PUT /api/:typeNum/events/:eventId/integrations/:integrationId
Content-Type: application/json

{
  "config": { ... full configuration object ... }
}

Response:
{
  "success": true,
  "integration": {
    "id": 123,
    "integrationType": "comeback_cash",
    "status": "pending",
    "config": { ... },
    "foreignId": 456
  }
}
```

#### Get Integration Details (for pre-populating wizard)
```
GET /api/:typeNum/events/:eventId/integrations/:integrationId

Response:
{
  "id": 123,
  "integrationType": "comeback_cash",
  "status": "pending",
  "config": { ... full config ... },
  "foreignId": 456,
  "createdAt": "2025-12-06T10:00:00Z",
  "updatedAt": "2025-12-06T10:30:00Z"
}
```

### Detail Page Integration Card Updates

The integration cards on the detail page will be enhanced to:

1. **Show Configuration Summary**: Key config values displayed inline
2. **Show Status Badges**: Using existing IntegrationBadge class (already implemented)
3. **Click to Open Wizard**: Full modal wizard opens on card click
4. **Quick Actions**: Edit (opens wizard), Disable (with confirmation)

```html
<div class="integration-card" data-integration-id="123" data-type="comeback_cash">
  <div class="integration-card-header">
    <span class="integration-icon comeback-cash"><i class="fa fa-ticket"></i></span>
    <div class="integration-card-info">
      <strong>Comeback Cash</strong>
      <span class="badge badge-new">New</span>
      <span class="badge badge-pending">Pending</span>
    </div>
    <button class="integration-card-action" title="Edit"><i class="fa fa-pencil"></i></button>
  </div>
  <div class="integration-card-summary">
    <span>Buy Side • Flat $5.00 • Redeem $3.00 off</span>
  </div>
</div>
```

### JavaScript Architecture

```javascript
// WizardModal.js - Base class
class WizardModal {
  constructor(integrationId, integrationType, config) {
    this.integrationId = integrationId;
    this.integrationType = integrationType;
    this.config = config;
    this.currentStep = 0;
    this.steps = this.getSteps();
  }

  getSteps() { /* Override in subclass */ }

  open() { /* Show modal, load step 1 */ }
  close() { /* Hide modal, confirm if unsaved changes */ }

  nextStep() { /* Validate current, advance */ }
  prevStep() { /* Go back */ }
  goToStep(n) { /* Navigate to step n */ }

  validate() { /* Validate current step */ }
  save() { /* API call to save config */ }

  render() { /* Render current step */ }
}

// ComebackCashWizard.js
class ComebackCashWizard extends WizardModal {
  getSteps() {
    return [
      { name: 'Side Selection', template: 'step-1-side', validate: this.validateSide },
      { name: 'Earning Config', template: 'step-2-earning', validate: this.validateEarning },
      { name: 'Redemption', template: 'step-3-redemption', validate: this.validateRedemption },
      { name: 'Review', template: 'step-4-review', validate: () => true }
    ];
  }

  validateSide() { return !!this.config.side; }
  validateEarning() { /* Earning type specific validation */ }
  validateRedemption() { /* Redemption validation */ }
}
```

### CSS Additions

```css
/* Wizard Modal Styles */
.wizard-modal {
  max-width: 720px;
  max-height: 85vh;
}

.wizard-steps {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  padding: 1rem;
  border-bottom: 1px solid var(--em-border-color);
}

.wizard-step-indicator {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8125rem;
  color: #64748b;
}

.wizard-step-indicator.active {
  color: #7c3aed;
  font-weight: 600;
}

.wizard-step-indicator.completed {
  color: #16a34a;
}

.wizard-step-dot {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 600;
  background: #f1f5f9;
  color: #64748b;
}

.wizard-step-indicator.active .wizard-step-dot {
  background: linear-gradient(135deg, #667eea, #764ba2);
  color: white;
}

.wizard-step-indicator.completed .wizard-step-dot {
  background: #dcfce7;
  color: #16a34a;
}

.wizard-body {
  padding: 1.5rem;
  overflow-y: auto;
  max-height: calc(85vh - 200px);
}

.wizard-footer {
  display: flex;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  border-top: 1px solid var(--em-border-color);
  background: #f8fafc;
}
```

### Migration from Original Phase 4

The following original Phase 4 items are **superseded**:

| Original Task | New Status |
|---------------|------------|
| T4.3.2 Modify detail.html for badge logic | Replaced by wizard modal integration |
| T4.3.3 Add expandable card for config summary | Replaced by wizard modal |
| T4.3.4 Add badge CSS classes | Keep - badges still needed |

The following are **retained**:
- IntegrationBadge model class (already implemented)
- Badge CSS classes (to be added)
- Status badge display logic

---

## Glossary

### Domain Terms

| Term | Definition | Context |
|------|------------|---------|
| Hub | Central view showing all 7 integration cards | Step 3 main view |
| Spoke | Modal form for configuring a single integration | Opens when clicking integration card |
| Integration | Connection between event and subsystem (CC, SMS, etc.) | 7 types defined in EventIntegration |
| Partial Success | Pattern where event succeeds even if integrations fail | Core design principle |

### Technical Terms

| Term | Definition | Context |
|------|------------|---------|
| IntegrationService | PHP service orchestrating all adapters | Business logic layer |
| Adapter | Class implementing IntegrationAdapterInterface | One per integration type |
| foreignId | ID of record created in target system | Stored in event_integrations |
| integrationResults | Array of per-integration status in API response | New response field |

### API/Interface Terms

| Term | Definition | Context |
|------|------------|---------|
| [API Term] | [Specific meaning in this context] | [Related endpoints or operations] |
| [Protocol/Format] | [Technical specification] | [Where used in integrations] |
