11 KiB
GiftedDialog Component Decomposition Plan
Overview
This document outlines a comprehensive plan to refactor the GiftedDialog component by breaking it into smaller, more manageable sub-components. This approach will improve maintainability, testability, and reusability while preparing the codebase for future Pinia integration.
Current State Analysis
The GiftedDialog component (1060 lines) is a complex Vue component that handles:
- Two-step wizard UI: Entity selection → Gift details
- Multiple entity types: Person/Project as giver/recipient
- Complex conditional rendering: Based on context and entity types
- Form validation and submission: Gift recording with API integration
- State management: UI flow, entity selection, form data
Key Challenges
- Large single file: Difficult to navigate and maintain
- Mixed concerns: UI logic, business logic, and API calls in one place
- Complex state: Multiple interconnected reactive properties
- Testing difficulty: Hard to test individual features in isolation
- Reusability: Components like entity grids could be reused elsewhere
Decomposition Strategy
Phase 1: Extract Display Components (✅ COMPLETED)
These components handle pure presentation with minimal business logic:
1. PersonCard.vue ✅
- Purpose: Display individual person entities with selection capability
- Features:
- Person avatar using EntityIcon
- Selection states (selectable, conflicted, disabled)
- Time icon overlay for contacts
- Click event handling
- Props:
person
,selectable
,conflicted
,showTimeIcon
- Emits:
person-selected
2. ProjectCard.vue ✅
- Purpose: Display individual project entities with selection capability
- Features:
- Project icon using ProjectIcon
- Project name and issuer information
- Click event handling
- Props:
project
,activeDid
,allMyDids
,allContacts
- Emits:
project-selected
3. EntitySummaryButton.vue ✅
- Purpose: Display selected entity with edit capability in step 2
- Features:
- Entity avatar (person or project)
- Entity name and role label
- Editable vs locked states
- Edit button functionality
- Props:
entity
,entityType
,label
,editable
- Emits:
edit-requested
4. AmountInput.vue ✅
- Purpose: Specialized numeric input with increment/decrement controls
- Features:
- Increment/decrement buttons with validation
- Configurable min/max values and step size
- Input validation and formatting
- v-model compatibility
- Props:
value
,min
,max
,step
,inputId
- Emits:
update:value
Phase 2: Extract Layout Components (✅ COMPLETED)
These components handle layout and entity organization:
5. EntityGrid.vue ✅
- Purpose: Unified grid layout for displaying people or projects
- Features:
- Responsive grid layout for people/projects
- Special entity integration (You, Unnamed)
- Conflict detection integration
- Empty state messaging
- Show All navigation
- Event delegation for entity selection
- Props:
entityType
,entities
,maxItems
,activeDid
,allMyDids
,allContacts
,conflictChecker
,showYouEntity
,youSelectable
,showAllRoute
,showAllQueryParams
- Emits:
entity-selected
6. SpecialEntityCard.vue ✅
- Purpose: Handle special entities like "You" and "Unnamed"
- Features:
- Special icon display (hand, question mark)
- Conflict state handling
- Configurable styling based on entity type
- Click event handling
- Props:
entityType
,label
,icon
,selectable
,conflicted
,entityData
- Emits:
entity-selected
7. ShowAllCard.vue ✅
- Purpose: Handle "Show All" navigation functionality
- Features:
- Router-link integration
- Query parameter passing
- Consistent visual styling
- Hover effects
- Props:
entityType
,routeName
,queryParams
- Emits: None (uses router-link)
Phase 3: Extract Step Components (FUTURE)
These components handle major UI sections:
7. EntitySelectionStep.vue (PLANNED)
- Purpose: Complete step 1 entity selection interface
- Features:
- Dynamic step type handling (giver/recipient)
- Entity type switching (people/projects)
- Conflict detection integration
- Cancel functionality
- Props:
stepType
,entityType
,entities
,conflicts
,context
- Emits:
entity-selected
,cancel
8. GiftDetailsStep.vue (PLANNED)
- Purpose: Complete step 2 gift details form
- Features:
- Entity summary display
- Gift description input
- Amount input with controls
- Form validation
- Submit functionality
- Props:
giver
,receiver
,context
,initialValues
- Emits:
submit
,back
,entity-edit-requested
Phase 4: Refactor Main Component (FINAL)
9. GiftedDialog.vue (PLANNED REFACTOR)
- Purpose: Orchestrate sub-components and manage overall state
- Responsibilities:
- Step navigation logic
- Entity conflict detection
- API integration for gift recording
- Success/error handling
- Dialog visibility management
Implementation Progress
✅ Completed Components
Phase 1: Display Components
- PersonCard.vue - Individual person display with selection
- ProjectCard.vue - Individual project display with selection
- EntitySummaryButton.vue - Selected entity display with edit capability
- AmountInput.vue - Numeric input with increment/decrement controls
Phase 2: Layout Components 5. EntityGrid.vue - Unified grid layout for entity selection 6. SpecialEntityCard.vue - Special entities (You, Unnamed) with conflict handling 7. ShowAllCard.vue - Show All navigation with router integration
🔄 Next Steps
- Update GiftedDialog.vue - Integrate Phase 1 & 2 components incrementally
- Test integration - Ensure functionality remains intact
- Create unit tests - For all new components
- Performance validation - Ensure no regression
📋 Future Phases
- Extract EntitySelectionStep.vue - Complete step 1 logic
- Extract GiftDetailsStep.vue - Complete step 2 logic
- Refactor main component - Minimal orchestration logic
- Add comprehensive tests - Unit tests for each component
- Prepare for Pinia - State management migration
Benefits of This Approach
1. Incremental Refactoring
- Each phase can be implemented and tested independently
- Reduces risk of breaking existing functionality
- Allows for gradual improvement over time
2. Improved Maintainability
- Smaller, focused components are easier to understand
- Clear separation of concerns
- Easier to locate and fix bugs
3. Enhanced Testability
- Individual components can be unit tested in isolation
- Easier to mock dependencies
- Better test coverage possible
4. Better Reusability
- Components like EntityGrid can be used in other views
- PersonCard and ProjectCard can be used throughout the app
- AmountInput can be reused for other numeric inputs
5. Pinia Preparation
- Smaller components make state management migration easier
- Clear data flow patterns emerge
- Easier to identify what state should be global vs local
Integration Examples
Using PersonCard in EntityGrid
<template>
<ul class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-x-2 gap-y-4">
<PersonCard
v-for="person in people"
:key="person.did"
:person="person"
:conflicted="wouldCreateConflict(person.did)"
@person-selected="handlePersonSelected"
/>
</ul>
</template>
Using AmountInput in GiftDetailsStep
<template>
<AmountInput
:value="amount"
:min="0"
:max="1000"
input-id="gift-amount"
@update:value="amount = $event"
/>
</template>
Using EntitySummaryButton in GiftDetailsStep
<template>
<div class="grid grid-cols-2 gap-2">
<EntitySummaryButton
:entity="giver"
entity-type="person"
label="Received from:"
:editable="canEditGiver"
@edit-requested="handleEditGiver"
/>
<EntitySummaryButton
:entity="receiver"
entity-type="person"
label="Given to:"
:editable="canEditReceiver"
@edit-requested="handleEditReceiver"
/>
</div>
</template>
Using EntityGrid in EntitySelectionStep
<template>
<div>
<label class="block font-bold mb-4">
{{ stepLabel }}
</label>
<EntityGrid
:entity-type="shouldShowProjects ? 'projects' : 'people'"
:entities="shouldShowProjects ? projects : allContacts"
:max-items="10"
:active-did="activeDid"
:all-my-dids="allMyDids"
:all-contacts="allContacts"
:conflict-checker="wouldCreateConflict"
:show-you-entity="showYouEntity"
:you-selectable="youSelectable"
:show-all-route="showAllRoute"
:show-all-query-params="showAllQueryParams"
@entity-selected="handleEntitySelected"
/>
</div>
</template>
Using SpecialEntityCard Standalone
<template>
<ul class="grid grid-cols-4 gap-2">
<SpecialEntityCard
entity-type="you"
label="You"
icon="hand"
:conflicted="wouldCreateConflict(activeDid)"
:entity-data="{ did: activeDid, name: 'You' }"
@entity-selected="handleYouSelected"
/>
<SpecialEntityCard
entity-type="unnamed"
label="Unnamed"
icon="circle-question"
:entity-data="{ did: '', name: 'Unnamed' }"
@entity-selected="handleUnnamedSelected"
/>
</ul>
</template>
Migration Strategy
Backward Compatibility
- Maintain existing API and prop interfaces
- Ensure all existing functionality works unchanged
- Preserve all event emissions and callbacks
Testing Strategy
- Create unit tests for each new component
- Maintain existing integration tests
- Add visual regression tests for UI components
Performance Considerations
- Monitor bundle size impact
- Ensure no performance regression
- Optimize component loading if needed
Security Considerations
Input Validation
- AmountInput includes proper numeric validation
- All user inputs are validated before processing
- XSS prevention through proper Vue templating
Data Handling
- No sensitive data stored in component state
- Proper prop validation and type checking
- Secure API communication maintained
Conclusion
This decomposition plan provides a structured approach to refactoring the GiftedDialog component while maintaining functionality and preparing for future enhancements. The incremental approach reduces risk and allows for continuous improvement of the codebase.
The completed Phase 1 components (PersonCard, ProjectCard, EntitySummaryButton, AmountInput) provide a solid foundation for the remaining phases and demonstrate the benefits of component decomposition in terms of maintainability, testability, and reusability.
Author: Matthew Raymer
Last Updated: 2025-01-28
Status: Phase 1 & 2 Complete, Integration Phase Next