Browse Source

Merge branch 'build-improvement' into performance-optimizations-testing

pull/159/head
Matthew Raymer 3 weeks ago
parent
commit
2e9b2ee58e
  1. 207
      docs/refactoring/GiftedDialog-EntityTypes-Refactoring.md
  2. 41
      src/components/EntitySelectionStep.vue
  3. 84
      src/components/GiftedDialog.vue
  4. 3
      src/views/ClaimView.vue
  5. 42
      src/views/ContactGiftingView.vue
  6. 6
      src/views/ContactsView.vue
  7. 6
      src/views/HomeView.vue
  8. 5
      src/views/ProjectViewView.vue

207
docs/refactoring/GiftedDialog-EntityTypes-Refactoring.md

@ -0,0 +1,207 @@
# GiftedDialog Entity Types Refactoring
## Overview
This refactoring simplifies the `GiftedDialog` component by replacing the complex `updateEntityTypes()` method with explicit props for entity types. This makes the component more declarative, reusable, and easier to understand.
## Problem
The original `updateEntityTypes()` method used multiple props (`showProjects`, `fromProjectId`, `toProjectId`, `recipientEntityTypeOverride`) to determine entity types through complex conditional logic:
```typescript
updateEntityTypes() {
// Reset and set entity types based on current context
this.giverEntityType = "person";
this.recipientEntityType = "person";
// If recipient entity type is explicitly overridden, use that
if (this.recipientEntityTypeOverride) {
this.recipientEntityType = this.recipientEntityTypeOverride;
}
// Determine entity types based on current context
if (this.showProjects) {
// HomeView "Project" button or ProjectViewView "Given by This"
this.giverEntityType = "project";
// Only override recipient if not already set by recipientEntityTypeOverride
if (!this.recipientEntityTypeOverride) {
this.recipientEntityType = "person";
}
} else if (this.fromProjectId) {
// ProjectViewView "Given by This" button (project is giver)
this.giverEntityType = "project";
// Only override recipient if not already set by recipientEntityTypeOverride
if (!this.recipientEntityTypeOverride) {
this.recipientEntityType = "person";
}
} else if (this.toProjectId) {
// ProjectViewView "Given to This" button (project is recipient)
this.giverEntityType = "person";
// Only override recipient if not already set by recipientEntityTypeOverride
if (!this.recipientEntityTypeOverride) {
this.recipientEntityType = "project";
}
} else {
// HomeView "Person" button
this.giverEntityType = "person";
// Only override recipient if not already set by recipientEntityTypeOverride
if (!this.recipientEntityTypeOverride) {
this.recipientEntityType = "person";
}
}
}
```
### Issues with the Original Approach
1. **Complex Logic**: Nested conditionals that were hard to follow
2. **Tight Coupling**: Views needed to understand internal logic to set the right props
3. **Inflexible**: Adding new entity type combinations required modifying the method
4. **Unclear Intent**: The relationship between props and entity types was not obvious
## Solution
### 1. Explicit Props
Replace the complex logic with explicit props:
```typescript
@Prop({ default: "person" }) giverEntityType = "person" as "person" | "project";
@Prop({ default: "person" }) recipientEntityType = "person" as "person" | "project";
```
### 2. Simple Inline Logic
Views now use simple inline logic to determine entity types:
```vue
<!-- HomeView -->
<GiftedDialog
ref="giftedDialog"
:giver-entity-type="showProjectsDialog ? 'project' : 'person'"
:recipient-entity-type="'person'"
/>
<!-- ProjectViewView -->
<GiftedDialog
ref="giveDialogToThis"
:giver-entity-type="'person'"
:recipient-entity-type="'project'"
:to-project-id="projectId"
:is-from-project-view="true"
/>
<!-- ClaimView -->
<GiftedDialog
ref="customGiveDialog"
:giver-entity-type="'person'"
:recipient-entity-type="projectInfo ? 'project' : 'person'"
:to-project-id="..."
/>
```
## Benefits
### 1. **Declarative**
- Entity types are explicitly declared in the template
- No hidden logic in watchers or complex methods
- Clear intent at the call site
### 2. **Reusable**
- Views can easily specify any combination of entity types
- No need to understand internal logic
- Simple inline logic is easy to understand
### 3. **Maintainable**
- Adding new entity type combinations is straightforward
- Logic is visible directly in the template
- No additional files to maintain
### 4. **Testable**
- Entity type logic is visible and predictable
- No complex state management to test
- Template logic can be easily verified
### 5. **Type Safe**
- TypeScript ensures correct entity type values
- Compile-time validation of entity type combinations
## Migration Guide
### For Views Using GiftedDialog
Simply update the template to use explicit entity type props:
```vue
<!-- Before -->
<GiftedDialog :show-projects="showProjects" />
<!-- After -->
<GiftedDialog
:giver-entity-type="showProjects ? 'project' : 'person'"
:recipient-entity-type="'person'"
/>
```
### Common Patterns
1. **Person-to-Person**: `giver-entity-type="'person'" recipient-entity-type="'person'"`
2. **Project-to-Person**: `giver-entity-type="'project'" recipient-entity-type="'person'"`
3. **Person-to-Project**: `giver-entity-type="'person'" recipient-entity-type="'project'"`
4. **Conditional Project**: `recipient-entity-type="hasProject ? 'project' : 'person'"`
## Files Changed
### Core Changes
- `src/components/GiftedDialog.vue` - Removed `updateEntityTypes()` method, added explicit props
### View Updates
- `src/views/HomeView.vue` - Updated to use inline logic
- `src/views/ProjectViewView.vue` - Updated to use inline logic
- `src/views/ClaimView.vue` - Updated to use inline logic
- `src/views/ContactGiftingView.vue` - Updated to use inline logic
- `src/views/ContactsView.vue` - Updated to use inline logic
## Backward Compatibility
The refactoring maintains backward compatibility by:
- Keeping all existing props that are still needed (`fromProjectId`, `toProjectId`, `isFromProjectView`)
- Preserving the same component API for the `open()` method
- Maintaining the same template structure
## Future Enhancements
1. **Validation**: Add runtime validation for entity type combinations
2. **Documentation**: Add JSDoc comments to the component props
3. **Testing**: Add unit tests for the component with different entity type combinations
## Conclusion
This refactoring transforms `GiftedDialog` from a component with complex internal logic to a declarative, reusable component. The explicit entity type props make the component's behavior clear and predictable, while the simple inline logic keeps the code straightforward and maintainable.
## Bug Fixes
### Issue 1: Entity Type Preservation in Navigation
**Problem**: When navigating from HomeView with `showProjects = true` to ContactGiftingView via "Show All", the entity type information was lost because `showAllQueryParams` returned an empty object for project contexts.
**Solution**: Modified `EntitySelectionStep.vue` to always pass entity type information in the query parameters, even for project contexts.
### Issue 2: Recipient Reset in ContactGiftingView
**Problem**: When selecting a giver in ContactGiftingView, the recipient was always reset to "You" instead of preserving the current recipient.
**Solution**: Updated ContactGiftingView to preserve the existing recipient from the context when selecting a giver, and enhanced the query parameter passing to include both giver and recipient information for better context preservation.
### Issue 3: HomeView Project Button Entity Type Mismatch
**Problem**: When navigating from HomeView Project button → change recipient → Show All → ContactGifting, the giver entity type was incorrectly set to "person" instead of "project".
**Root Cause**: ContactGiftingView was inferring entity types from `fromProjectId` and `toProjectId` instead of using the explicitly passed `giverEntityType` and `recipientEntityType` from the query parameters.
**Solution**: Updated ContactGiftingView to use the explicitly passed entity types from query parameters instead of inferring them from project IDs.
### Files Modified for Bug Fixes
- `src/components/EntitySelectionStep.vue` - Enhanced query parameter passing
- `src/views/ContactGiftingView.vue` - Improved context preservation logic and entity type handling

41
src/components/EntitySelectionStep.vue

@ -234,11 +234,7 @@ export default class EntitySelectionStep extends Vue {
* Query parameters for "Show All" navigation * Query parameters for "Show All" navigation
*/ */
get showAllQueryParams(): Record<string, string> { get showAllQueryParams(): Record<string, string> {
if (this.shouldShowProjects) { const baseParams = {
return {};
}
return {
stepType: this.stepType, stepType: this.stepType,
giverEntityType: this.giverEntityType, giverEntityType: this.giverEntityType,
recipientEntityType: this.recipientEntityType, recipientEntityType: this.recipientEntityType,
@ -247,26 +243,31 @@ export default class EntitySelectionStep extends Vue {
amountInput: this.amountInput, amountInput: this.amountInput,
unitCode: this.unitCode, unitCode: this.unitCode,
offerId: this.offerId, offerId: this.offerId,
...(this.stepType === "giver"
? {
recipientProjectId: this.toProjectId || "",
recipientProjectName: this.receiver?.name || "",
recipientProjectImage: this.receiver?.image || "",
recipientProjectHandleId: this.receiver?.handleId || "",
recipientDid: this.receiver?.did || "",
}
: {
giverProjectId: this.fromProjectId || "",
giverProjectName: this.giver?.name || "",
giverProjectImage: this.giver?.image || "",
giverProjectHandleId: this.giver?.handleId || "",
giverDid: this.giver?.did || "",
}),
fromProjectId: this.fromProjectId, fromProjectId: this.fromProjectId,
toProjectId: this.toProjectId, toProjectId: this.toProjectId,
showProjects: this.showProjects.toString(), showProjects: this.showProjects.toString(),
isFromProjectView: this.isFromProjectView.toString(), isFromProjectView: this.isFromProjectView.toString(),
}; };
if (this.shouldShowProjects) {
// For project contexts, still pass entity type information
return baseParams;
}
return {
...baseParams,
// Always pass both giver and recipient info for context preservation
giverProjectId: this.fromProjectId || "",
giverProjectName: this.giver?.name || "",
giverProjectImage: this.giver?.image || "",
giverProjectHandleId: this.giver?.handleId || "",
giverDid: this.giver?.did || "",
recipientProjectId: this.toProjectId || "",
recipientProjectName: this.receiver?.name || "",
recipientProjectImage: this.receiver?.image || "",
recipientProjectHandleId: this.receiver?.handleId || "",
recipientDid: this.receiver?.did || "",
};
} }
/** /**

84
src/components/GiftedDialog.vue

@ -3,7 +3,7 @@
<div <div
class="dialog" class="dialog"
data-testid="gifted-dialog" data-testid="gifted-dialog"
:data-recipient-entity-type-override="recipientEntityTypeOverride" :data-recipient-entity-type="recipientEntityType"
> >
<!-- Step 1: Entity Selection --> <!-- Step 1: Entity Selection -->
<EntitySelectionStep <EntitySelectionStep
@ -11,7 +11,9 @@
:step-type="stepType" :step-type="stepType"
:giver-entity-type="giverEntityType" :giver-entity-type="giverEntityType"
:recipient-entity-type="recipientEntityType" :recipient-entity-type="recipientEntityType"
:show-projects="showProjects" :show-projects="
giverEntityType === 'project' || recipientEntityType === 'project'
"
:is-from-project-view="isFromProjectView" :is-from-project-view="isFromProjectView"
:projects="projects" :projects="projects"
:all-contacts="allContacts" :all-contacts="allContacts"
@ -60,7 +62,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Vue, Component, Prop, Watch } from "vue-facing-decorator"; import { Vue, Component, Prop } from "vue-facing-decorator";
import { import {
createAndSubmitGive, createAndSubmitGive,
@ -105,29 +107,13 @@ export default class GiftedDialog extends Vue {
@Prop() fromProjectId = ""; @Prop() fromProjectId = "";
@Prop() toProjectId = ""; @Prop() toProjectId = "";
@Prop({ default: false }) showProjects = false;
@Prop() isFromProjectView = false; @Prop() isFromProjectView = false;
@Prop() recipientEntityTypeOverride?: "person" | "project"; @Prop({ default: "person" }) giverEntityType = "person" as
| "person"
@Watch("showProjects") | "project";
onShowProjectsChange() { @Prop({ default: "person" }) recipientEntityType = "person" as
this.updateEntityTypes(); | "person"
} | "project";
@Watch("fromProjectId")
onFromProjectIdChange() {
this.updateEntityTypes();
}
@Watch("toProjectId")
onToProjectIdChange() {
this.updateEntityTypes();
}
@Watch("recipientEntityTypeOverride")
onRecipientEntityTypeOverrideChange() {
this.updateEntityTypes();
}
activeDid = ""; activeDid = "";
allContacts: Array<Contact> = []; allContacts: Array<Contact> = [];
@ -203,50 +189,6 @@ export default class GiftedDialog extends Vue {
} }
stepType = "giver"; stepType = "giver";
giverEntityType = "person" as "person" | "project";
recipientEntityType = "person" as "person" | "project";
updateEntityTypes() {
// Reset and set entity types based on current context
this.giverEntityType = "person";
this.recipientEntityType = "person";
// If recipient entity type is explicitly overridden, use that
if (this.recipientEntityTypeOverride) {
this.recipientEntityType = this.recipientEntityTypeOverride;
}
// Determine entity types based on current context
if (this.showProjects) {
// HomeView "Project" button or ProjectViewView "Given by This"
this.giverEntityType = "project";
// Only override recipient if not already set by recipientEntityTypeOverride
if (!this.recipientEntityTypeOverride) {
this.recipientEntityType = "person";
}
} else if (this.fromProjectId) {
// ProjectViewView "Given by This" button (project is giver)
this.giverEntityType = "project";
// Only override recipient if not already set by recipientEntityTypeOverride
if (!this.recipientEntityTypeOverride) {
this.recipientEntityType = "person";
}
} else if (this.toProjectId) {
// ProjectViewView "Given to This" button (project is recipient)
this.giverEntityType = "person";
// Only override recipient if not already set by recipientEntityTypeOverride
if (!this.recipientEntityTypeOverride) {
this.recipientEntityType = "project";
}
} else {
// HomeView "Person" button
this.giverEntityType = "person";
// Only override recipient if not already set by recipientEntityTypeOverride
if (!this.recipientEntityTypeOverride) {
this.recipientEntityType = "person";
}
}
}
async open( async open(
giver?: libsUtil.GiverReceiverInputInfo, giver?: libsUtil.GiverReceiverInputInfo,
@ -261,7 +203,6 @@ export default class GiftedDialog extends Vue {
this.giver = giver; this.giver = giver;
this.receiver = receiver; this.receiver = receiver;
this.offerId = offerId || ""; this.offerId = offerId || "";
console.log("offerId", this.offerId);
this.prompt = prompt || ""; this.prompt = prompt || "";
this.description = description || ""; this.description = description || "";
this.amountInput = amountInput || "0"; this.amountInput = amountInput || "0";
@ -270,8 +211,7 @@ export default class GiftedDialog extends Vue {
this.firstStep = !giver; this.firstStep = !giver;
this.stepType = "giver"; this.stepType = "giver";
// Update entity types based on current props // Entity types are now set via props, no need to call updateEntityTypes()
this.updateEntityTypes();
try { try {
const settings = await this.$settings(); const settings = await this.$settings();

3
src/views/ClaimView.vue

@ -201,12 +201,13 @@
</div> </div>
<GiftedDialog <GiftedDialog
ref="customGiveDialog" ref="customGiveDialog"
:giver-entity-type="'person'"
:recipient-entity-type="projectInfo ? 'project' : 'person'"
:to-project-id=" :to-project-id="
detailsForGive?.fulfillsPlanHandleId || detailsForGive?.fulfillsPlanHandleId ||
detailsForOffer?.fulfillsPlanHandleId || detailsForOffer?.fulfillsPlanHandleId ||
'' ''
" "
:recipient-entity-type-override="projectInfo ? 'project' : 'person'"
/> />
<div v-if="libsUtil.isGiveAction(veriClaim)"> <div v-if="libsUtil.isGiveAction(veriClaim)">

42
src/views/ContactGiftingView.vue

@ -67,9 +67,10 @@
<GiftedDialog <GiftedDialog
ref="giftedDialog" ref="giftedDialog"
:giver-entity-type="giverEntityType"
:recipient-entity-type="recipientEntityType"
:from-project-id="fromProjectId" :from-project-id="fromProjectId"
:to-project-id="toProjectId" :to-project-id="toProjectId"
:show-projects="showProjects"
:is-from-project-view="isFromProjectView" :is-from-project-view="isFromProjectView"
/> />
</section> </section>
@ -213,7 +214,7 @@ export default class ContactGiftingView extends Vue {
// Preserve the existing giver from the context // Preserve the existing giver from the context
if (this.giverEntityType === "project") { if (this.giverEntityType === "project") {
giver = { giver = {
// no did, because it's a project did: this.giverProjectHandleId,
name: this.giverProjectName, name: this.giverProjectName,
image: this.giverProjectImage, image: this.giverProjectImage,
handleId: this.giverProjectHandleId, handleId: this.giverProjectHandleId,
@ -253,7 +254,7 @@ export default class ContactGiftingView extends Vue {
// We're selecting a giver, so the contact becomes the giver // We're selecting a giver, so the contact becomes the giver
giver = contact as GiverReceiverInputInfo; // Safe because we know contact is not "Unnamed" or undefined giver = contact as GiverReceiverInputInfo; // Safe because we know contact is not "Unnamed" or undefined
// Recipient is either a project or the current user // Preserve the existing recipient from the context
if (this.recipientEntityType === "project") { if (this.recipientEntityType === "project") {
recipient = { recipient = {
did: this.recipientProjectHandleId, did: this.recipientProjectHandleId,
@ -262,7 +263,20 @@ export default class ContactGiftingView extends Vue {
handleId: this.recipientProjectHandleId, handleId: this.recipientProjectHandleId,
}; };
} else { } else {
recipient = { did: this.activeDid, name: "You" }; // Check if the preserved recipient was "Unnamed" (empty DID) or a regular contact
if (this.recipientDid === "") {
// Recipient was "Unnamed"
recipient = { did: "", name: "Unnamed" };
} else if (this.recipientDid) {
// Recipient was a regular contact
recipient = {
did: this.recipientDid,
name: this.recipientProjectName || "Someone",
};
} else {
// Fallback to current user
recipient = { did: this.activeDid, name: "You" };
}
} }
} else { } else {
// We're selecting a recipient, so the contact becomes the recipient // We're selecting a recipient, so the contact becomes the recipient
@ -276,13 +290,21 @@ export default class ContactGiftingView extends Vue {
image: this.giverProjectImage, image: this.giverProjectImage,
handleId: this.giverProjectHandleId, handleId: this.giverProjectHandleId,
}; };
} else if (this.giverDid) {
giver = {
did: this.giverDid,
name: this.giverProjectName || "Someone",
};
} else { } else {
giver = { did: this.activeDid, name: "You" }; // Check if the preserved giver was "Unnamed" (empty DID) or a regular contact
if (this.giverDid === "") {
// Giver was "Unnamed"
giver = { did: "", name: "Unnamed" };
} else if (this.giverDid) {
// Giver was a regular contact
giver = {
did: this.giverDid,
name: this.giverProjectName || "Someone",
};
} else {
// Fallback to current user
giver = { did: this.activeDid, name: "You" };
}
} }
} }

6
src/views/ContactsView.vue

@ -107,7 +107,11 @@
@copy-selected="copySelectedContacts" @copy-selected="copySelectedContacts"
/> />
<GiftedDialog ref="customGivenDialog" /> <GiftedDialog
ref="customGivenDialog"
:giver-entity-type="'person'"
:recipient-entity-type="'person'"
/>
<OfferDialog ref="customOfferDialog" /> <OfferDialog ref="customOfferDialog" />
<ContactNameDialog ref="contactNameDialog" /> <ContactNameDialog ref="contactNameDialog" />

6
src/views/HomeView.vue

@ -151,7 +151,11 @@ Raymer * @version 1.0.0 */
</div> </div>
</div> </div>
<GiftedDialog ref="giftedDialog" :show-projects="showProjectsDialog" /> <GiftedDialog
ref="giftedDialog"
:giver-entity-type="showProjectsDialog ? 'project' : 'person'"
:recipient-entity-type="'person'"
/>
<GiftedPrompts ref="giftedPrompts" /> <GiftedPrompts ref="giftedPrompts" />
<FeedFilters ref="feedFilters" /> <FeedFilters ref="feedFilters" />

5
src/views/ProjectViewView.vue

@ -216,6 +216,8 @@
<GiftedDialog <GiftedDialog
ref="giveDialogToThis" ref="giveDialogToThis"
:giver-entity-type="'person'"
:recipient-entity-type="'project'"
:to-project-id="projectId" :to-project-id="projectId"
:is-from-project-view="true" :is-from-project-view="true"
/> />
@ -486,8 +488,9 @@
</div> </div>
<GiftedDialog <GiftedDialog
ref="giveDialogFromThis" ref="giveDialogFromThis"
:giver-entity-type="'project'"
:recipient-entity-type="'person'"
:from-project-id="projectId" :from-project-id="projectId"
:show-projects="true"
:is-from-project-view="true" :is-from-project-view="true"
/> />

Loading…
Cancel
Save