# 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.vue
Main Orchestrator] subgraph "Step 1: Entity Selection" ESS[EntitySelectionStep.vue
Step Controller] EG[EntityGrid.vue
Layout Manager] PC[PersonCard.vue
Person Display] PRC[ProjectCard.vue
Project Display] SEC[SpecialEntityCard.vue
Special Entities] SAC[ShowAllCard.vue
Navigation] end subgraph "Step 2: Gift Details" GDS[GiftDetailsStep.vue
Form Controller] ESB[EntitySummaryButton.vue
Entity Summary] AI[AmountInput.vue
Amount Control] end subgraph "External Dependencies" EI[EntityIcon.vue
Icon Renderer] PI[ProjectIcon.vue
Project 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 ``` ### Project Context Usage ```vue ``` ### 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 ``` ### 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(/)<[^<]*)*<\/script>/gi, ''); // Check length limits if (sanitized.length > 500) { return false; } return true; } validateAmount(amount: number): boolean { // Check numeric bounds if (amount < 0 || amount > 10000) { return false; } // Check for valid number if (!Number.isFinite(amount)) { return false; } return true; } ``` ### DID Privacy Protection ```typescript // Ensure DIDs are only shared with authorized contacts function shouldShowDid(contact: Contact, currentUser: string): boolean { // Only show DID if user has explicitly authorized this contact return contact.registeredByDid === currentUser || contact.did === currentUser || contact.isPublic === true; } ``` ### API Security ```typescript // Secure API communication async function submitGift(giftData: GiftData): Promise { // Validate data before sending if (!this.validateGiftData(giftData)) { throw new Error('Invalid gift data'); } // Sign request with user's private key const signedRequest = await this.signRequest(giftData); // Send to endorser server with proper headers const response = await fetch('/api/claims', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.getAuthToken()}`, 'X-API-Version': '1.0' }, body: JSON.stringify(signedRequest) }); if (!response.ok) { throw new Error('Failed to submit gift'); } } ``` --- ## Conclusion The GiftedDialog system represents a sophisticated, well-architected solution for gift recording in the TimeSafari application. Through careful decomposition into focused components, comprehensive state management, and thoughtful user experience design, it provides a robust foundation for community gift tracking while maintaining privacy, security, and accessibility standards. The modular architecture ensures maintainability and testability, while the flexible integration patterns allow for seamless embedding in various contexts throughout the application. The comprehensive documentation and testing strategies ensure long-term sustainability and ease of development. **Author**: Matthew Raymer **Last Updated**: 2025-06-30 **Version**: 1.0.0