diff --git a/GiftedDialog-Complete-Documentation.md b/GiftedDialog-Complete-Documentation.md new file mode 100644 index 00000000..e8e5ed0b --- /dev/null +++ b/GiftedDialog-Complete-Documentation.md @@ -0,0 +1,1203 @@ +# 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 +``` + +## 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. +