# GiftedDialog Complete Documentation ## Table of Contents 1. [Overview](#overview) 2. [Architecture Diagram](#architecture-diagram) 3. [Component Hierarchy](#component-hierarchy) 4. [Data Flow Diagrams](#data-flow-diagrams) 5. [Component Documentation](#component-documentation) 6. [Integration Patterns](#integration-patterns) 7. [State Management](#state-management) 8. [Event Flow](#event-flow) 9. [Usage Examples](#usage-examples) 10. [Testing Strategy](#testing-strategy) ## Overview The GiftedDialog system is a sophisticated multi-step dialog for recording gifts between people and projects in the TimeSafari application. It consists of a main orchestrating component and 9 specialized child components that handle different aspects of the gift recording workflow. ### Key Features - **Two-Step Workflow**: Entity selection → Gift details - **Multi-Entity Support**: People, projects, and special entities - **Conflict Detection**: Prevents invalid gift combinations - **Responsive Design**: Works across all device sizes - **Accessibility**: Full keyboard navigation and screen reader support - **Validation**: Comprehensive form validation and error handling - **Flexible Integration**: Can be embedded in any view with different contexts ### Design Principles - **Single Responsibility**: Each component has one clear purpose - **Composition over Inheritance**: Built through component composition - **Reactive Data Flow**: Vue's reactivity system for state management - **Progressive Enhancement**: Core functionality works without JavaScript - **Privacy by Design**: Respects user privacy and data sovereignty ## Architecture Diagram ```mermaid graph TB subgraph "GiftedDialog System" GD[GiftedDialog.vueMain Orchestrator] subgraph "Step 1: Entity Selection" ESS[EntitySelectionStep.vueStep Controller] EG[EntityGrid.vueLayout Manager] PC[PersonCard.vuePerson Display] PRC[ProjectCard.vueProject Display] SEC[SpecialEntityCard.vueSpecial Entities] SAC[ShowAllCard.vueNavigation] end subgraph "Step 2: Gift Details" GDS[GiftDetailsStep.vueForm Controller] ESB[EntitySummaryButton.vueEntity Summary] AI[AmountInput.vueAmount Control] end subgraph "External Dependencies" EI[EntityIcon.vueIcon Renderer] PI[ProjectIcon.vueProject Icons] DB[(Database Layer)] API[Endorser API] ROUTER[Vue Router] end end GD --> ESS GD --> GDS ESS --> EG EG --> PC EG --> PRC EG --> SEC EG --> SAC GDS --> ESB GDS --> AI PC --> EI PRC --> PI ESB --> EI ESB --> PI GD --> DB GD --> API SAC --> ROUTER ``` ### ASCII Art Architecture Diagram ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ GiftedDialog System │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ GiftedDialog.vue │ │ │ │ Main Orchestrator │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ Dialog State │ │ Step Control │ │ API Integration│ │ │ │ │ │ Management │ │ (1 ↔ 2) │ │ & Validation │ │ │ │ │ │ │ │ │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Step 1: Entity Selection │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ EntitySelectionStep.vue │ │ │ │ │ │ Step Controller │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ Context │ │ Conflict │ │ Dynamic │ │ │ │ │ │ │ │ Awareness │ │ Detection │ │ Labeling │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ EntityGrid.vue │ │ │ │ │ │ Layout Manager │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ Responsive │ │ Event │ │ Empty State │ │ │ │ │ │ │ │ Grid Layout │ │ Delegation │ │ Handling │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ Entity Display Components │ │ │ │ │ │ ┌─────────── ──┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ PersonCard │ │ProjectCard │ │SpecialEntity│ │ │ │ │ │ │ │ Avatar, Name │ │Icon, Name │ │Card (You, │ │ │ │ │ │ │ │ Selection │ │Issuer Info │ │Unnamed) │ │ │ │ │ │ │ └──────────── ─┘ └─────────────┘ └─────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ ShowAllCard.vue │ │ │ │ │ │ Navigation Component │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ Router │ │ Query │ │ Context │ │ │ │ │ │ │ │ Integration │ │ Parameters │ │ Preservation│ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Step 2: Gift Details │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ GiftDetailsStep.vue │ │ │ │ │ │ Form Controller │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ Form │ │ Validation │ │ Conflict │ │ │ │ │ │ │ │ Management │ │ & Error │ │ Warning │ │ │ │ │ │ │ │ │ │ Handling │ │ Display │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ Form Input Components │ │ │ │ │ │ ┌──────────── ─┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │EntitySummary │ │AmountInput │ │Description │ │ │ │ │ │ │ │Button │ │Increment/ │ │Input │ │ │ │ │ │ │ │Edit Capability│ │Decrement │ │Placeholder │ │ │ │ │ │ │ └─────────── ──┘ └─────────────┘ └─────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ External Dependencies │ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ EntityIcon │ │ProjectIcon │ │ Database │ │ Endorser │ │ │ │ │ │ Avatar │ │ Project │ │ Layer │ │ API │ │ │ │ │ │ Renderer │ │ Images │ │ SQLite │ │ Claims │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ Vue Router │ │ │ │ │ │ Navigation & Deep Linking │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ Data Flow: ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ User │──▶│ GiftedDialog│──▶│ Step 1 │──▶│ Entity │ │ Interaction │ │ Main │ │ Selection │ │ Selection │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Success/ │◀──│ API │◀──│ Step 2 │◀──│ Form │ │ Error │ │ Integration │ │ Details │ │ Submission │ │ Handling │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ Component Communication: ┌─────────────────────────────────────────────────────────────────────────────┐ │ Event Flow: entity-selected → update:amount → submit → gift-recorded │ │ │ │ State Flow: Dialog State → Step State → Form State → API State │ │ │ │ Data Flow: Props Down → Events Up → Callbacks → Parent Notification │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ## Component Hierarchy ### Main Component - **GiftedDialog.vue** - Main orchestrating component ### Step Components - **EntitySelectionStep.vue** - Step 1 controller - **GiftDetailsStep.vue** - Step 2 controller ### Layout Components - **EntityGrid.vue** - Unified entity grid layout - **EntitySummaryButton.vue** - Selected entity display ### Display Components - **PersonCard.vue** - Individual person display - **ProjectCard.vue** - Individual project display - **SpecialEntityCard.vue** - Special entities (You, Unnamed) - **ShowAllCard.vue** - Navigation component ### Input Components - **AmountInput.vue** - Numeric input with controls ## Data Flow Diagrams ### Step 1: Entity Selection Flow ```mermaid sequenceDiagram participant User participant GiftedDialog participant EntitySelectionStep participant EntityGrid participant PersonCard participant SpecialEntityCard User->>GiftedDialog: Open dialog GiftedDialog->>EntitySelectionStep: Render step 1 EntitySelectionStep->>EntityGrid: Pass entities & config EntityGrid->>PersonCard: Render people EntityGrid->>SpecialEntityCard: Render special entities User->>PersonCard: Click person PersonCard->>EntityGrid: emit person-selected EntityGrid->>EntitySelectionStep: emit entity-selected EntitySelectionStep->>GiftedDialog: emit entity-selected GiftedDialog->>GiftedDialog: Update state & advance to step 2 ``` ### Step 2: Gift Details Flow ```mermaid sequenceDiagram participant User participant GiftedDialog participant GiftDetailsStep participant EntitySummaryButton participant AmountInput GiftedDialog->>GiftDetailsStep: Render step 2 GiftDetailsStep->>EntitySummaryButton: Display selected entities GiftDetailsStep->>AmountInput: Render amount input User->>AmountInput: Click increment AmountInput->>GiftDetailsStep: emit update:value GiftDetailsStep->>GiftedDialog: emit update:amount GiftedDialog->>GiftedDialog: Update amount state User->>GiftDetailsStep: Click submit GiftDetailsStep->>GiftedDialog: emit submit GiftedDialog->>GiftedDialog: Process & submit gift ``` ### Conflict Detection Flow ```mermaid flowchart TD A[User Selects Entity] --> B{Is Person-to-Person?} B -->|No| F[Allow Selection] B -->|Yes| C{Same DID?} C -->|No| F C -->|Yes| D[Mark as Conflicted] D --> E[Disable Selection] F --> G[Enable Selection] G --> H[Proceed to Next Step] E --> I[Show Conflict Warning] ``` ## Component Documentation ### 1. GiftedDialog.vue **Purpose**: Main orchestrating component that manages the overall dialog state and workflow. **Key Responsibilities**: - Dialog visibility and lifecycle management - Step navigation (1 → 2) - Entity conflict detection - API integration for gift submission - Success/error handling - Child component coordination **Props**: ```typescript interface GiftedDialogProps { fromProjectId?: string; // Project ID when project is giver toProjectId?: string; // Project ID when project is recipient showProjects?: boolean; // Whether to show projects isFromProjectView?: boolean; // Context flag for project views } ``` **Key Methods**: - `open()` - Initialize and show dialog - `cancel()` - Close dialog and reset state - `confirm()` - Submit gift and handle response - `handleEntitySelected()` - Process entity selection from step 1 - `handleSubmit()` - Process form submission from step 2 **State Management**: ```typescript interface GiftedDialogState { visible: boolean; currentStep: number; giver?: GiverReceiverInputInfo; receiver?: GiverReceiverInputInfo; description: string; amountInput: string; unitCode: string; // ... additional state properties } ``` ### 2. EntitySelectionStep.vue **Purpose**: Complete step 1 interface for entity selection with dynamic labeling and context awareness. **Key Features**: - Dynamic step labeling based on context - EntityGrid integration for unified display - Conflict detection and prevention - Special entity handling - Context preservation for navigation **Props**: ```typescript interface EntitySelectionStepProps { stepType: 'giver' | 'recipient'; giverEntityType: 'person' | 'project'; recipientEntityType: 'person' | 'project'; showProjects: boolean; isFromProjectView: boolean; projects: PlanData[]; allContacts: Contact[]; activeDid: string; allMyDids: string[]; conflictChecker: (did: string) => boolean; fromProjectId?: string; toProjectId?: string; giver?: any; receiver?: any; } ``` **Computed Properties**: - `stepLabel` - Dynamic label based on context - `shouldShowProjects` - Whether to show projects vs people - `shouldShowYouEntity` - Whether to show "You" option - `youSelectable` - Whether "You" can be selected - `showAllRoute` - Navigation route for "Show All" - `showAllQueryParams` - Query parameters for navigation ### 3. GiftDetailsStep.vue **Purpose**: Complete step 2 interface for gift details with form validation and entity summaries. **Key Features**: - Entity summary display with edit capability - Gift description input with placeholder support - Amount input with increment/decrement controls - Unit code selection (HUR, USD, BTC, etc.) - Conflict detection and warning display - Form validation and submission **Props**: ```typescript interface GiftDetailsStepProps { giver: EntityData | null; receiver: EntityData | null; giverEntityType: 'person' | 'project'; recipientEntityType: 'person' | 'project'; description: string; amount: number; unitCode: string; prompt: string; isFromProjectView: boolean; hasConflict: boolean; offerId: string; fromProjectId: string; toProjectId: string; } ``` **Local State**: ```typescript interface GiftDetailsLocalState { localDescription: string; localAmount: number; localUnitCode: string; } ``` ### 4. EntityGrid.vue **Purpose**: Unified grid layout for displaying people, projects, and special entities with responsive design. **Key Features**: - Responsive grid layout (3-6 columns based on entity type) - Special entity integration (You, Unnamed) - Conflict detection integration - Empty state messaging - Show All navigation - Event delegation for entity selection **Props**: ```typescript interface EntityGridProps { entityType: 'people' | 'projects'; entities: Contact[] | PlanData[]; maxItems: number; activeDid: string; allMyDids: string[]; allContacts: Contact[]; conflictChecker: (did: string) => boolean; showYouEntity: boolean; youSelectable: boolean; showAllRoute: string; showAllQueryParams: Record; } ``` **Grid Layout**: - **People**: 4-6 columns (responsive) - **Projects**: 3-4 columns (responsive) - **Special entities**: Always shown for people grids - **Show All**: Shown when entities > maxItems ### 5. PersonCard.vue **Purpose**: Individual person display with avatar, name, and selection states. **Key Features**: - EntityIcon integration for avatar display - Conflict state visualization - Time icon display for activity context - Hover and selection states - Accessibility support **Props**: ```typescript interface PersonCardProps { person: Contact; conflicted: boolean; showTimeIcon: boolean; } ``` **Visual States**: - **Normal**: Default appearance - **Conflicted**: Red border, disabled state - **Hovered**: Elevated appearance - **Selected**: Highlighted border ### 6. ProjectCard.vue **Purpose**: Individual project display with project icon, name, and issuer information. **Key Features**: - ProjectIcon integration for project images - Issuer information display - Project name with ellipsis overflow - Selection state management - Accessibility support **Props**: ```typescript interface ProjectCardProps { project: PlanData; activeDid: string; allMyDids: string[]; allContacts: Contact[]; } ``` **Display Elements**: - Project icon/image - Project name (truncated) - Issuer information - Selection indicator ### 7. SpecialEntityCard.vue **Purpose**: Special entity display for "You" and "Unnamed" with conflict detection. **Key Features**: - FontAwesome icon integration - Conflict state handling - Conditional selectability - Consistent styling with other cards - Accessibility support **Props**: ```typescript interface SpecialEntityCardProps { entityType: 'you' | 'unnamed'; label: string; icon: string; selectable: boolean; conflicted: boolean; entityData: any; } ``` **Entity Types**: - **You**: Represents the current user - **Unnamed**: Represents anonymous giver ### 8. ShowAllCard.vue **Purpose**: Navigation component for "Show All" functionality with router integration. **Key Features**: - Vue Router integration - Query parameter passing - Context preservation - Consistent styling - Icon-based navigation **Props**: ```typescript interface ShowAllCardProps { entityType: 'people' | 'projects'; routeName: string; queryParams: Record; } ``` **Navigation Logic**: - People → ContactGiftingView with context - Projects → DiscoverView - Query parameters preserve dialog state ### 9. EntitySummaryButton.vue **Purpose**: Selected entity display with edit capability for step 2. **Key Features**: - EntityIcon/ProjectIcon integration - Edit button functionality - Entity type detection - Consistent styling - Accessibility support **Props**: ```typescript interface EntitySummaryButtonProps { entity: EntityData | null; entityType: 'person' | 'project'; label: string; editable: boolean; } ``` **Display Elements**: - Entity icon/avatar - Entity name - Edit button (when editable) - Type-specific styling ### 10. AmountInput.vue **Purpose**: Specialized numeric input with increment/decrement controls and validation. **Key Features**: - Increment/decrement buttons - Configurable min/max values and step size - Input validation and formatting - Disabled state handling for boundary values - v-model compatibility **Props**: ```typescript interface AmountInputProps { value: number; min: number; max: number; step: number; inputId: string; } ``` **Key Methods**: - `increment()` - Increase value by step - `decrement()` - Decrease value by step - `handleInput()` - Process direct input - `handleBlur()` - Validate on blur **Validation Logic**: - Minimum/maximum bounds checking - Step size validation - Numeric input sanitization - Real-time feedback ## Integration Patterns ### 1. Dialog Opening Pattern ```typescript // From any parent component const giftedDialog = this.$refs.giftedDialog as GiftedDialog; // Basic opening giftedDialog.open(); // With pre-selected entities giftedDialog.open( giverEntity, // Pre-selected giver receiverEntity, // Pre-selected receiver offerId, // Offer context customTitle, // Custom dialog title prompt, // Custom input prompt successCallback // Success handler ); ``` ### 2. Context-Aware Opening ```typescript // From project view - project as giver giftedDialog.open( projectEntity, // Project as giver undefined, // User selects receiver undefined, // No offer "Gift from Project", "What did this project provide?" ); // From project view - project as receiver giftedDialog.open( undefined, // User selects giver projectEntity, // Project as receiver undefined, // No offer "Gift to Project", "What was contributed to this project?" ); ``` ### 3. Event Handling Pattern ```typescript // In parent component export default class ParentComponent extends Vue { handleGiftRecorded(amount: number) { // Handle successful gift recording this.$notify({ title: "Gift Recorded", text: `Successfully recorded gift of ${amount} hours`, type: "success" }); // Refresh data if needed this.loadActivities(); } openGiftDialog() { const dialog = this.$refs.giftedDialog as GiftedDialog; dialog.open( undefined, undefined, undefined, undefined, undefined, this.handleGiftRecorded ); } } ``` ## State Management ### Internal State Flow ```mermaid stateDiagram-v2 [*] --> Closed Closed --> Step1 : open() Step1 --> Step2 : entity selected Step2 --> Step1 : edit entity Step2 --> Submitting : submit form Submitting --> Success : API success Submitting --> Error : API error Success --> Closed : auto-close Error --> Step2 : retry Step1 --> Closed : cancel Step2 --> Closed : cancel ``` ### Entity Type Determination ```mermaid flowchart TD A[Dialog Opens] --> B{fromProjectId set?} B -->|Yes| C[Giver = Project] B -->|No| D{toProjectId set?} D -->|Yes| E[Receiver = Project] D -->|No| F{showProjects true?} F -->|Yes| G[Show Project Selection] F -->|No| H[Show Person Selection] C --> I[Show Person Selection for Receiver] E --> J[Show Person Selection for Giver] G --> K[User Selects Project Type] H --> L[User Selects Person] ``` ### Conflict Detection Logic ```typescript // Conflict detection algorithm function wouldCreateConflict(selectedDid: string): boolean { // Only applies to person-to-person gifts if (giverEntityType !== "person" || recipientEntityType !== "person") { return false; } // Check if selecting same person for both roles if (stepType === "giver") { return receiver?.did === selectedDid; } else if (stepType === "recipient") { return giver?.did === selectedDid; } return false; } ``` ## Event Flow ### Entity Selection Events ```mermaid sequenceDiagram participant Card as PersonCard/ProjectCard participant Grid as EntityGrid participant Step as EntitySelectionStep participant Dialog as GiftedDialog Card->>Grid: emit entity-selected Grid->>Step: emit entity-selected Step->>Dialog: emit entity-selected Dialog->>Dialog: handleEntitySelected() Dialog->>Dialog: updateState() Dialog->>Dialog: advanceToStep2() ``` ### Form Submission Events ```mermaid sequenceDiagram participant Form as GiftDetailsStep participant Dialog as GiftedDialog participant API as Endorser API participant Parent as Parent Component Form->>Dialog: emit submit Dialog->>Dialog: handleSubmit() Dialog->>API: createAndSubmitGive() API->>Dialog: response Dialog->>Dialog: handleSuccess/Error() Dialog->>Parent: callbackOnSuccess() Dialog->>Dialog: close() ``` ### Amount Input Events ```mermaid sequenceDiagram participant Input as AmountInput participant Step as GiftDetailsStep participant Dialog as GiftedDialog Input->>Step: emit update:value Step->>Dialog: emit update:amount Dialog->>Dialog: handleAmountUpdate() Dialog->>Dialog: updateAmountInput() ``` ## Usage Examples ### Basic Usage ```vue Record Gift ``` ### Project Context Usage ```vue Gift from Project Gift to Project ``` ### Advanced Integration ```vue ``` ## Testing Strategy ### Unit Testing ```typescript // AmountInput.spec.ts import { mount } from '@vue/test-utils'; import AmountInput from '@/components/AmountInput.vue'; describe('AmountInput', () => { it('increments value when increment button clicked', async () => { const wrapper = mount(AmountInput, { props: { value: 5, min: 0, max: 10, step: 1 } }); const incrementButton = wrapper.find('[data-testid="increment-button"]'); await incrementButton.trigger('click'); expect(wrapper.emitted('update:value')).toBeTruthy(); expect(wrapper.emitted('update:value')![0]).toEqual([6]); }); it('disables decrement button at minimum value', () => { const wrapper = mount(AmountInput, { props: { value: 0, min: 0, max: 10, step: 1 } }); const decrementButton = wrapper.find('[data-testid="decrement-button"]'); expect(decrementButton.attributes('disabled')).toBeDefined(); }); }); ``` ### Integration Testing ```typescript // GiftedDialog.spec.ts import { mount } from '@vue/test-utils'; import GiftedDialog from '@/components/GiftedDialog.vue'; describe('GiftedDialog Integration', () => { it('completes full gift recording workflow', async () => { const wrapper = mount(GiftedDialog, { props: { showProjects: false } }); // Open dialog await wrapper.vm.open(); expect(wrapper.vm.visible).toBe(true); expect(wrapper.vm.currentStep).toBe(1); // Select giver const personCard = wrapper.find('[data-testid="person-card-0"]'); await personCard.trigger('click'); // Should advance to step 2 expect(wrapper.vm.currentStep).toBe(2); expect(wrapper.vm.giver).toBeDefined(); // Fill form const descriptionInput = wrapper.find('[data-testid="description-input"]'); await descriptionInput.setValue('Test gift'); const amountInput = wrapper.find('[data-testid="amount-input"]'); await amountInput.setValue('5'); // Submit form const submitButton = wrapper.find('[data-testid="submit-button"]'); await submitButton.trigger('click'); // Should emit success event expect(wrapper.emitted('gift-recorded')).toBeTruthy(); }); }); ``` ### End-to-End Testing ```typescript // giftedDialog.e2e.ts import { test, expect } from '@playwright/test'; test('Gift recording workflow', async ({ page }) => { await page.goto('/'); // Open gift dialog await page.click('[data-testid="record-gift-button"]'); // Step 1: Select entities await expect(page.locator('.dialog')).toBeVisible(); await expect(page.locator('text=Choose a person received from:')).toBeVisible(); // Select a person await page.click('[data-testid="person-card"]:first-child'); // Step 2: Fill details await expect(page.locator('text=What was given?')).toBeVisible(); await page.fill('[data-testid="description-input"]', 'Helped with coding'); // Increment amount await page.click('[data-testid="increment-button"]'); await page.click('[data-testid="increment-button"]'); // Submit await page.click('[data-testid="submit-button"]'); // Verify success await expect(page.locator('.notification.success')).toBeVisible(); await expect(page.locator('.dialog')).not.toBeVisible(); }); ``` ## Performance Considerations ### Optimization Strategies 1. **Lazy Loading**: Components are loaded only when needed 2. **Virtual Scrolling**: For large entity lists 3. **Debounced Input**: Amount input changes are debounced 4. **Computed Properties**: Efficient reactive calculations 5. **Event Delegation**: Minimal event listeners ### Memory Management ```typescript // Cleanup in component destruction export default class GiftedDialog extends Vue { beforeUnmount() { // Clear references this.callbackOnSuccess = undefined; this.allContacts = []; this.projects = []; // Cancel pending API requests this.cancelPendingRequests(); } } ``` ### Bundle Size Optimization - Tree-shaking for unused FontAwesome icons - Dynamic imports for heavy components - Shared dependencies between components - Minimal external dependencies ## Accessibility Features ### Keyboard Navigation - Tab order follows visual flow - Enter/Space for button activation - Escape to close dialog - Arrow keys for amount input ### Screen Reader Support ```vue Record Gift Choose who gave and received the gift, then provide details. Gift Description Describe what was given or the service provided ``` ### Focus Management ```typescript export default class GiftedDialog extends Vue { private previousFocus: HTMLElement | null = null; open() { // Store current focus this.previousFocus = document.activeElement as HTMLElement; // Show dialog this.visible = true; // Focus first interactive element this.$nextTick(() => { const firstInput = this.$el.querySelector('button, input, select'); if (firstInput) { (firstInput as HTMLElement).focus(); } }); } cancel() { this.visible = false; // Restore previous focus if (this.previousFocus) { this.previousFocus.focus(); } } } ``` ## Security Considerations ### Input Validation ```typescript // Sanitize and validate all inputs validateDescription(description: string): boolean { // Remove potentially dangerous characters const sanitized = description.replace(/
Choose who gave and received the gift, then provide details.