Merge branch 'master' into contact-gifting-current-user

This commit is contained in:
Jose Olarte III
2025-08-11 19:01:06 +08:00
89 changed files with 8631 additions and 2343 deletions

View File

@@ -1,13 +1,19 @@
<template>
<div v-if="visible" class="dialog-overlay">
<div class="dialog">
<div
class="dialog"
data-testid="gifted-dialog"
:data-recipient-entity-type="recipientEntityType"
>
<!-- Step 1: Entity Selection -->
<EntitySelectionStep
v-show="firstStep"
:step-type="stepType"
:giver-entity-type="giverEntityType"
:recipient-entity-type="recipientEntityType"
:show-projects="showProjects"
:show-projects="
giverEntityType === 'project' || recipientEntityType === 'project'
"
:is-from-project-view="isFromProjectView"
:projects="projects"
:all-contacts="allContacts"
@@ -18,6 +24,10 @@
:to-project-id="toProjectId"
:giver="giver"
:receiver="receiver"
:description="description"
:amount-input="amountInput"
:unit-code="unitCode"
:offer-id="offerId"
:notify="$notify"
:hide-show-all="hideShowAll"
@entity-selected="handleEntitySelected"
@@ -53,7 +63,7 @@
</template>
<script lang="ts">
import { Vue, Component, Prop, Watch } from "vue-facing-decorator";
import { Vue, Component, Prop } from "vue-facing-decorator";
import {
createAndSubmitGive,
@@ -72,6 +82,12 @@ import GiftDetailsStep from "../components/GiftDetailsStep.vue";
import { PlanData } from "../interfaces/records";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import {
NOTIFY_GIFT_ERROR_NEGATIVE_AMOUNT,
NOTIFY_GIFT_ERROR_NO_DESCRIPTION,
NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER,
NOTIFY_GIFTED_DETAILS_RECORDING_GIVE,
} from "@/constants/notifications";
@Component({
components: {
@@ -98,24 +114,13 @@ export default class GiftedDialog extends Vue {
@Prop() fromProjectId = "";
@Prop() toProjectId = "";
@Prop({ default: false }) showProjects = false;
@Prop() isFromProjectView = false;
@Prop({ default: false }) hideShowAll = false;
@Watch("showProjects")
onShowProjectsChange() {
this.updateEntityTypes();
}
@Watch("fromProjectId")
onFromProjectIdChange() {
this.updateEntityTypes();
}
@Watch("toProjectId")
onToProjectIdChange() {
this.updateEntityTypes();
}
@Prop({ default: "person" }) giverEntityType = "person" as
| "person"
| "project";
@Prop({ default: "person" }) recipientEntityType = "person" as
| "person"
| "project";
activeDid = "";
allContacts: Array<Contact> = [];
@@ -124,20 +129,19 @@ export default class GiftedDialog extends Vue {
amountInput = "0";
callbackOnSuccess?: (amount: number) => void = () => {};
customTitle?: string;
description = "";
firstStep = true; // true = Step 1 (giver/recipient selection), false = Step 2 (amount/description)
giver?: libsUtil.GiverReceiverInputInfo; // undefined means no identified giver agent
offerId = "";
projects: PlanData[] = [];
prompt = "";
receiver?: libsUtil.GiverReceiverInputInfo;
stepType = "giver";
unitCode = "HUR";
visible = false;
libsUtil = libsUtil;
projects: PlanData[] = [];
didInfo = didInfo;
// Computed property to help debug template logic
@@ -191,56 +195,27 @@ export default class GiftedDialog extends Vue {
return false;
}
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";
// Determine entity types based on current context
if (this.showProjects) {
// HomeView "Project" button or ProjectViewView "Given by This"
this.giverEntityType = "project";
this.recipientEntityType = "person";
} else if (this.fromProjectId) {
// ProjectViewView "Given by This" button (project is giver)
this.giverEntityType = "project";
this.recipientEntityType = "person";
} else if (this.toProjectId) {
// ProjectViewView "Given to This" button (project is recipient)
this.giverEntityType = "person";
this.recipientEntityType = "project";
} else {
// HomeView "Person" button
this.giverEntityType = "person";
this.recipientEntityType = "person";
}
}
async open(
giver?: libsUtil.GiverReceiverInputInfo,
receiver?: libsUtil.GiverReceiverInputInfo,
offerId?: string,
customTitle?: string,
prompt?: string,
description?: string,
amountInput?: string,
unitCode?: string,
callbackOnSuccess: (amount: number) => void = () => {},
) {
this.customTitle = customTitle;
this.giver = giver;
this.prompt = prompt || "";
this.receiver = receiver;
this.amountInput = "0";
this.callbackOnSuccess = callbackOnSuccess;
this.offerId = offerId || "";
this.prompt = prompt || "";
this.description = description || "";
this.amountInput = amountInput || "0";
this.unitCode = unitCode || "HUR";
this.callbackOnSuccess = callbackOnSuccess;
this.firstStep = !giver;
this.stepType = "giver";
// Update entity types based on current props
this.updateEntityTypes();
try {
const settings = await this.$settings();
this.apiServer = settings.apiServer || "";
@@ -320,23 +295,24 @@ export default class GiftedDialog extends Vue {
async confirm() {
if (!this.activeDid) {
this.safeNotify.error(
"You must select an identifier before you can record a give.",
TIMEOUTS.STANDARD,
NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER.message,
TIMEOUTS.SHORT,
);
return;
}
if (parseFloat(this.amountInput) < 0) {
this.safeNotify.error(
"You may not send a negative number.",
NOTIFY_GIFT_ERROR_NEGATIVE_AMOUNT.message,
TIMEOUTS.SHORT,
);
return;
}
if (!this.description && !parseFloat(this.amountInput)) {
this.safeNotify.error(
`You must enter a description or some number of ${
this.libsUtil.UNIT_LONG[this.unitCode]
}.`,
NOTIFY_GIFT_ERROR_NO_DESCRIPTION.message.replace(
"{unit}",
this.libsUtil.UNIT_SHORT[this.unitCode] || this.unitCode,
),
TIMEOUTS.SHORT,
);
return;
@@ -352,7 +328,11 @@ export default class GiftedDialog extends Vue {
}
this.close();
this.safeNotify.toast("Recording the give...", undefined, TIMEOUTS.BRIEF);
this.safeNotify.toast(
NOTIFY_GIFTED_DETAILS_RECORDING_GIVE.message,
undefined,
TIMEOUTS.BRIEF,
);
// this is asynchronous, but we don't need to wait for it to complete
await this.recordGive(
(this.giver?.did as string) || null,
@@ -479,10 +459,13 @@ export default class GiftedDialog extends Vue {
name: contact.name || contact.did,
};
} else {
this.giver = {
did: "",
name: "Unnamed",
};
// Only set to "Unnamed" if no giver is currently set
if (!this.giver || !this.giver.did) {
this.giver = {
did: "",
name: "Unnamed",
};
}
}
this.firstStep = false;
}
@@ -492,6 +475,10 @@ export default class GiftedDialog extends Vue {
this.firstStep = true;
}
moveToStep2() {
this.firstStep = false;
}
async loadProjects() {
try {
const response = await fetch(this.apiServer + "/api/v2/report/plans", {
@@ -534,10 +521,13 @@ export default class GiftedDialog extends Vue {
name: contact.name || contact.did,
};
} else {
this.receiver = {
did: "",
name: "Unnamed",
};
// Only set to "Unnamed" if no receiver is currently set
if (!this.receiver || !this.receiver.did) {
this.receiver = {
did: "",
name: "Unnamed",
};
}
}
this.firstStep = false;
}
@@ -561,16 +551,13 @@ export default class GiftedDialog extends Vue {
giverName: this.giver?.name,
offerId: this.offerId,
fulfillsProjectId:
this.giverEntityType === "person" &&
this.recipientEntityType === "project"
? this.toProjectId
: undefined,
this.recipientEntityType === "project" ? this.toProjectId : undefined,
providerProjectId:
this.giverEntityType === "project" &&
this.recipientEntityType === "person"
this.giverEntityType === "project"
? this.giver?.handleId
: this.fromProjectId,
recipientDid: this.receiver?.did,
recipientDid:
this.recipientEntityType === "person" ? this.receiver?.did : undefined,
recipientName: this.receiver?.name,
unitCode: this.unitCode,
};