Files
crowd-funder-for-time-pwa/src/views/GiftedDetailsView.vue
T

935 lines
28 KiB
Vue

<template>
<QuickNav />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<TopMessage />
<div class="mb-8">
<!-- Sub View Heading -->
<div id="SubViewHeading" class="flex gap-4 items-start mb-4">
<h1 class="grow text-xl text-center font-semibold leading-tight">
What Was Given
</h1>
<!-- Back -->
<a
class="order-first text-lg text-center leading-none p-1"
@click="cancelBack()"
>
<font-awesome icon="chevron-left" class="block text-center w-[1em]" />
</a>
<!-- Help button -->
<router-link
:to="{ name: 'help' }"
class="block ms-auto text-sm text-center text-white bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-1.5 rounded-full"
>
<font-awesome icon="question" class="block text-center w-[1em]" />
</router-link>
</div>
<h2 class="text-lg font-normal leading-tight text-center overflow-hidden">
<div class="truncate">
From
{{
givenByProject()
? providerProjectName
: // check for DID because name could be "Unnamed"
givenByPerson() && giverDid
? giverName
: "someone not named"
}}
</div>
<div class="truncate">
to
{{
givenToProject()
? fulfillsProjectName
: // check for DID because name could be "Unnamed"
givenToPerson() && recipientDid
? recipientName
: "someone not named"
}}
</div>
</h2>
</div>
<textarea
v-model="description"
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
placeholder="What was received"
/>
<div class="flex mb-4">
<AmountInput
:value="parseFloat(amountInput) || 0"
:min="0"
input-id="inputGivenAmount"
:on-update-value="handleAmountChange"
/>
<select
v-model="unitCode"
class="flex-1 rounded border border-slate-400 ms-2 px-3 py-2"
>
<option
v-for="(displayName, code) in unitOptions"
:key="code"
:value="code"
>
{{ displayName }}
</option>
</select>
</div>
<div class="flex justify-center mt-4" data-testId="imagery">
<span v-if="imageUrl" class="flex items-end gap-3">
<a :href="imageUrl" target="_blank">
<img :src="imageUrl" class="h-36 rounded-lg" />
</a>
<font-awesome
icon="trash-can"
class="text-red-500 fa-fw cursor-pointer"
@click="confirmDeleteImage"
/>
</span>
<span v-else>
<font-awesome
icon="camera"
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
@click="openImageDialog"
/>
</span>
</div>
<ImageMethodDialog ref="imageDialog" default-camera-mode="environment" />
<div class="mt-4 sm:flex justify-between gap-2">
<!-- First Column for Giver -->
<div
class="sm:flex-grow sm:w-1/2 border border-slate-400 p-2 rounded-md overflow-hidden"
>
<!-- Giver Selection Display -->
<div
v-if="!showGiverSelection"
class="cursor-pointer"
@click="openGiverSelection"
>
<div class="flex items-center">
<label class="text-sm flex-1">
{{
givenByProject() && providerProjectName
? "From " + providerProjectName
: givenByPerson() && giverName
? "From " + giverName
: "Unnamed giver"
}}
</label>
<span class="text-sm text-blue-500">Change</span>
<font-awesome icon="chevron-right" class="text-blue-500 ms-2" />
</div>
</div>
<!-- Giver Selection Interface -->
<div v-if="showGiverSelection">
<EntitySelectionStep
step-type="giver"
:all-contacts="allContacts"
:active-did="activeDid"
:all-my-dids="allMyDids"
:conflict-checker="wouldCreateConflictWithRecipient"
:giver="currentGiver"
:receiver="currentReceiver"
:notify="$notify"
@entity-selected="handleGiverEntitySelected"
@cancel="closeGiverSelection"
/>
</div>
</div>
<div class="sm:flex-shrink flex justify-center items-center my-1 sm:my-0">
<font-awesome
icon="arrow-right"
class="fa-fw h-7 rotate-90 sm:rotate-0"
/>
</div>
<!-- Third Column for Recipient -->
<div
class="sm:flex-grow sm:w-1/2 border border-slate-400 p-2 rounded-md overflow-hidden"
>
<!-- Recipient Selection Display -->
<div
v-if="!showRecipientSelection"
class="cursor-pointer"
@click="openRecipientSelection"
>
<div class="flex items-center">
<label class="text-sm flex-1">
{{
givenToProject() && fulfillsProjectName
? "To " + fulfillsProjectName
: givenToPerson() && recipientName
? "To " + recipientName
: "Unnamed recipient"
}}
</label>
<span class="text-sm text-blue-500">Change</span>
<font-awesome icon="chevron-right" class="text-blue-500 ms-2" />
</div>
</div>
<!-- Recipient Selection Interface -->
<div v-if="showRecipientSelection">
<EntitySelectionStep
step-type="recipient"
:all-contacts="allContacts"
:active-did="activeDid"
:all-my-dids="allMyDids"
:conflict-checker="wouldCreateConflictWithGiver"
:giver="currentGiver"
:receiver="currentReceiver"
:notify="$notify"
@entity-selected="handleRecipientEntitySelected"
@cancel="closeRecipientSelection"
/>
</div>
</div>
</div>
<div v-if="showGeneralAdvanced" class="mt-4 flex">
<router-link
:to="{
name: 'claim-add-raw',
query: {
claim: constructGiveParam(),
},
}"
class="text-blue-500"
>
Edit Raw Data
</router-link>
</div>
<p class="text-center text-sm my-4">
<b class="font-medium">Sign &amp; Send</b> to publish to the world
<font-awesome
icon="circle-info"
class="fa-fw text-blue-500 text-base cursor-pointer"
@click="explainData()"
/>
</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
@click="confirm"
>
Sign &amp; Send
</button>
<button
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="cancel"
>
Cancel
</button>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import ImageMethodDialog from "../components/ImageMethodDialog.vue";
import QuickNav from "../components/QuickNav.vue";
import TopMessage from "../components/TopMessage.vue";
import AmountInput from "../components/AmountInput.vue";
import EntitySelectionStep from "../components/EntitySelectionStep.vue";
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "../constants/app";
import { GenericCredWrapper, GiveActionClaim } from "../interfaces";
import {
createAndSubmitGive,
didInfo,
editAndSubmitGive,
getHeaders,
getPlanFromCache,
hydrateGive,
} from "../libs/endorserServer";
import * as libsUtil from "../libs/util";
import { retrieveAccountDids } from "../libs/util";
import { logger } from "../utils/logger";
import { Contact } from "@/db/tables/contacts";
import { PlanData } from "../interfaces/records";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import { showSeedPhraseReminder } from "@/utils/seedPhraseReminder";
import {
NOTIFY_GIFTED_DETAILS_RETRIEVAL_ERROR,
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_CONFIRM,
NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER,
NOTIFY_GIFT_ERROR_NEGATIVE_AMOUNT,
NOTIFY_GIFTED_DETAILS_RECORDING_GIVE,
NOTIFY_GIFTED_DETAILS_CREATE_GIVE_ERROR,
NOTIFY_GIFTED_DETAILS_GIFT_RECORDED,
NOTIFY_GIFT_ERROR_NO_DESCRIPTION,
} from "@/constants/notifications";
@Component({
components: {
ImageMethodDialog,
QuickNav,
TopMessage,
AmountInput,
EntitySelectionStep,
},
mixins: [PlatformServiceMixin],
})
export default class GiftedDetails extends Vue {
$route!: RouteLocationNormalizedLoaded;
$router!: Router;
$notify!: (notification: NotificationIface, timeout?: number) => void;
notify!: ReturnType<typeof createNotifyHelpers>;
activeDid = "";
apiServer = "";
allContacts: Array<Contact> = [];
allMyDids: Array<string> = [];
amountInput = "0";
description = "";
destinationPathAfter = "";
fulfillsProjectId = "";
fulfillsProjectName = "a project";
giverDid = "";
giverName = "";
hideBackButton = false;
imageUrl = "";
imageUrlToDelete = "";
message = "";
offerId = "";
prevCredToEdit?: GenericCredWrapper<GiveActionClaim>;
providerProjectId = "";
providerProjectName = "a project";
recipientDid = "";
recipientName = "";
showGeneralAdvanced = false;
showGiverSelection = false;
showRecipientSelection = false;
unitCode = "HUR";
libsUtil = libsUtil;
created() {
this.notify = createNotifyHelpers(this.$notify);
}
async mounted() {
try {
this.prevCredToEdit = (this.$route.query["prevCredToEdit"] as string)
? (JSON.parse(
this.$route.query["prevCredToEdit"] as string,
) as GenericCredWrapper<GiveActionClaim>)
: undefined;
} catch (error) {
this.notify.error(
NOTIFY_GIFTED_DETAILS_RETRIEVAL_ERROR.message,
TIMEOUTS.LONG,
);
}
const prevAmount = this.prevCredToEdit?.claim?.object?.amountOfThisGood;
this.amountInput =
(this.$route.query["amountInput"] as string) ||
(prevAmount ? String(prevAmount) : "") ||
this.amountInput;
this.description =
(this.$route.query["description"] as string) ||
this.prevCredToEdit?.claim?.description ||
this.description;
this.destinationPathAfter =
(this.$route.query["destinationPathAfter"] as string) || "";
this.giverDid = ((this.$route.query["giverDid"] as string) ||
(this.prevCredToEdit?.claim?.agent as unknown as { identifier: string })
?.identifier ||
this.giverDid) as string;
this.giverName = (this.$route.query["giverName"] as string) || "";
this.hideBackButton =
(this.$route.query["hideBackButton"] as string) === "true";
this.message = (this.$route.query["message"] as string) || "";
// find any offer ID
const fulfills = this.prevCredToEdit?.claim?.fulfills;
const fulfillsArray = Array.isArray(fulfills)
? fulfills
: fulfills
? [fulfills]
: [];
const offer = fulfillsArray.find((rec) => rec["@type"] === "Offer");
this.offerId = ((this.$route.query["offerId"] as string) ||
offer?.identifier ||
this.offerId) as string;
// find any fulfills project ID
const fulfillsProject = fulfillsArray.find(
(rec) => rec["@type"] === "PlanAction",
);
// eslint-disable-next-line prettier/prettier
this.fulfillsProjectId =
((this.$route.query["fulfillsProjectId"] as string) ||
fulfillsProject?.identifier ||
this.fulfillsProjectId) as string;
// find any provider project ID
const provider = this.prevCredToEdit?.claim?.provider;
const providerArray = Array.isArray(provider)
? provider
: provider
? [provider]
: [];
const providerProject = providerArray.find(
(rec) => rec["@type"] === "PlanAction",
);
this.providerProjectId = ((this.$route.query[
"providerProjectId"
] as string) ||
providerProject?.identifier ||
this.providerProjectId) as string;
this.recipientDid = ((this.$route.query["recipientDid"] as string) ||
this.prevCredToEdit?.claim?.recipient?.identifier) as string;
this.recipientName = (this.$route.query["recipientName"] as string) || "";
this.unitCode = ((this.$route.query["unitCode"] as string) ||
this.prevCredToEdit?.claim?.object?.unitCode ||
this.unitCode) as string;
this.imageUrl = ((this.$route.query["imageUrl"] as string) ||
this.prevCredToEdit?.claim?.image ||
this.imageUrl) as string;
// this is an endpoint for sharing project info to highlight something given
// https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
if (this.$route.query["shareTitle"] as string) {
this.description =
((this.$route.query["shareTitle"] as string) || "") +
(this.description ? "\n" + this.description : "");
}
if (this.$route.query["shareText"] as string) {
this.description =
(this.description ? this.description + "\n" : "") +
((this.$route.query["shareText"] as string) || "");
}
if (this.$route.query["shareUrl"] as string) {
this.imageUrl = this.$route.query["shareUrl"] as string;
}
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.allContacts = await this.$contactsByDateAdded();
this.allMyDids = await retrieveAccountDids();
if (
(this.giverDid && !this.giverName) ||
(this.recipientDid && !this.recipientName)
) {
if (this.giverDid && !this.giverName) {
this.giverName = didInfo(
this.giverDid,
this.activeDid,
this.allMyDids,
this.allContacts,
);
}
if (this.recipientDid && !this.recipientName) {
this.recipientName = didInfo(
this.recipientDid,
this.activeDid,
this.allMyDids,
this.allContacts,
);
}
}
this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
if (this.fulfillsProjectId) {
const fulfillsProject = await getPlanFromCache(
this.fulfillsProjectId,
this.axios,
this.apiServer,
this.activeDid,
);
this.fulfillsProjectName = fulfillsProject?.name
? `the project "${fulfillsProject.name}"`
: "a project";
}
if (this.providerProjectId) {
const providerProject = await getPlanFromCache(
this.providerProjectId,
this.axios,
this.apiServer,
this.activeDid,
);
this.providerProjectName = providerProject?.name
? `the project "${providerProject.name}"`
: "a project";
}
}
givenByPerson() {
return !!this.giverDid;
}
givenByProject() {
return !!this.providerProjectId;
}
givenToPerson() {
return !!this.recipientDid;
}
givenToProject() {
return !!this.fulfillsProjectId;
}
changeUnitCode() {
const units = Object.keys(this.libsUtil.UNIT_SHORT);
const index = units.indexOf(this.unitCode);
this.unitCode = units[(index + 1) % units.length];
}
increment() {
this.amountInput = `${(parseFloat(this.amountInput) || 0) + 1}`;
}
decrement() {
this.amountInput = `${Math.max(
0,
(parseFloat(this.amountInput) || 1) - 1,
)}`;
}
handleAmountChange(value: number): void {
this.amountInput = value.toString();
}
cancel() {
// Only delete freshly uploaded images, not ones from an existing claim
if (this.imageUrl && this.imageUrl !== this.prevCredToEdit?.claim?.image) {
this.deleteImage(this.imageUrl); // not awaiting, so they'll go back immediately
}
if (this.destinationPathAfter) {
(this.$router as Router).push({ path: this.destinationPathAfter });
} else {
(this.$router as Router).back();
}
}
cancelBack() {
// Only delete freshly uploaded images, not ones from an existing claim
if (this.imageUrl && this.imageUrl !== this.prevCredToEdit?.claim?.image) {
this.deleteImage(this.imageUrl); // not awaiting, so they'll go back immediately
}
(this.$router as Router).back();
}
openImageDialog() {
(this.$refs.imageDialog as ImageMethodDialog).open((imgUrl) => {
this.imageUrl = imgUrl;
}, "GiveAction");
}
confirmDeleteImage() {
this.notify.confirm(
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_CONFIRM.message,
async () => {
// Stage the image for deletion on submit rather than deleting immediately,
// so that canceling the edit doesn't destroy the referenced image.
this.imageUrlToDelete = this.imageUrl;
this.imageUrl = "";
},
TIMEOUTS.LONG,
);
}
async deleteImage(imageUrl: string) {
if (!imageUrl) {
return;
}
try {
const headers = await getHeaders(this.activeDid);
if (
window.location.hostname === "localhost" &&
!DEFAULT_IMAGE_API_SERVER.includes("localhost")
) {
logger.log(
"Using shared image API server, so only users on that server can play with images.",
);
}
const response = await this.axios.delete(
DEFAULT_IMAGE_API_SERVER + "/image/" + encodeURIComponent(imageUrl),
{ headers },
);
if (response.status === 204) {
// don't bother with a notification
} else {
logger.error("Problem deleting image:", response);
}
} catch (error) {
logger.error("Error deleting image:", error);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((error as any)?.response?.status === 404) {
logger.log("Image was already deleted:", error);
} else {
logger.error("Failed to delete image from server:", error);
}
}
}
async confirm() {
if (!this.activeDid) {
this.notify.error(
NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER.message,
TIMEOUTS.SHORT,
);
return;
}
if (parseFloat(this.amountInput) < 0) {
this.notify.error(
NOTIFY_GIFT_ERROR_NEGATIVE_AMOUNT.message,
TIMEOUTS.SHORT,
);
return;
}
if (!this.description && !parseFloat(this.amountInput)) {
this.notify.error(
NOTIFY_GIFT_ERROR_NO_DESCRIPTION.message.replace(
"{unit}",
this.libsUtil.UNIT_SHORT[this.unitCode] || this.unitCode,
),
TIMEOUTS.SHORT,
);
return;
}
this.notify.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();
}
notifyUserOfRecipient() {
// there's no individual recipient or there's a fulfills project
if (!this.recipientDid) {
this.notify.warning(
"To assign a recipient, you must choose a person in a previous step.",
TIMEOUTS.SHORT,
);
} else {
// must be because givenToProject() is true
this.notify.warning(
"You cannot assign both to a recipient and to a project.",
TIMEOUTS.SHORT,
);
}
}
notifyUserOfProvidingProject() {
// we're here because they clicked and either there is no provider project or there is a giver chosen
if (!this.providerProjectId) {
this.notify.warning(
"To select a project as a provider, you must choose a project in a previous step.",
TIMEOUTS.SHORT,
);
} else {
// no providing project was chosen, so there must be an individual giver
this.notify.warning(
"You cannot select both a giving project and person.",
TIMEOUTS.SHORT,
);
}
}
notifyUserFulfillsProject() {
// we're here because they clicked and either there is no fulfills project or there is a recipient chosen
if (!this.fulfillsProjectId) {
this.notify.warning(
"To assign a project as a recipient, you must choose a project in a previous step.",
TIMEOUTS.SHORT,
);
} else {
// no fulfills project was chosen, so there must be an individual recipient
this.notify.warning(
"You cannot select both a receiving project and person.",
TIMEOUTS.SHORT,
);
}
}
/**
*
* @param giverDid may be null
* @param description may be an empty string
* @param amountInput may be 0
* @param unitCode may be omitted, defaults to "HUR"
*/
public async recordGive() {
try {
let result;
if (this.prevCredToEdit) {
// don't create from a blank one in case some properties were set from a different interface
result = await editAndSubmitGive(
this.axios,
this.apiServer,
this.prevCredToEdit,
this.activeDid,
this.giverDid,
this.recipientDid,
this.description,
parseFloat(this.amountInput),
this.unitCode,
this.fulfillsProjectId,
this.offerId,
false,
this.imageUrl,
this.providerProjectId,
);
} else {
result = await createAndSubmitGive(
this.axios,
this.apiServer,
this.activeDid,
this.giverDid,
this.recipientDid,
this.description,
parseFloat(this.amountInput),
this.unitCode,
this.fulfillsProjectId,
this.offerId,
false,
this.imageUrl,
this.providerProjectId,
);
}
if (!result.success) {
const errorMessage = result.error;
logger.error("Error with give creation result:", result);
this.notify.error(
errorMessage || NOTIFY_GIFTED_DETAILS_CREATE_GIVE_ERROR.message,
TIMEOUTS.LONG,
);
} else {
// Delete the old image from storage now that the edit is saved
if (this.imageUrlToDelete) {
this.deleteImage(this.imageUrlToDelete); // not awaiting
this.imageUrlToDelete = "";
}
this.notify.success(
NOTIFY_GIFTED_DETAILS_GIFT_RECORDED.message,
TIMEOUTS.SHORT,
);
// Show seed phrase backup reminder if needed
try {
const settings = await this.$accountSettings();
showSeedPhraseReminder(!!settings.hasBackedUpSeed, this.$notify);
} catch (error) {
logger.error("Error checking seed backup status:", error);
}
if (this.destinationPathAfter) {
(this.$router as Router).push({ path: this.destinationPathAfter });
} else {
(this.$router as Router).back();
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
logger.error("Error with give recordation caught:", error);
const errorMessage =
error.userMessage ||
error.response?.data?.error?.message ||
"There was an error recording the give.";
this.notify.error(errorMessage, TIMEOUTS.LONG);
}
}
constructGiveParam() {
const giveClaim = hydrateGive(
this.prevCredToEdit?.claim as GiveActionClaim,
this.giverDid,
this.recipientDid,
this.description,
parseFloat(this.amountInput),
this.unitCode,
this.fulfillsProjectId,
this.offerId,
false,
this.imageUrl,
this.providerProjectId,
this.prevCredToEdit?.id as string,
);
const claimStr = JSON.stringify(giveClaim);
return claimStr;
}
// Helper functions for readability
explainData() {
this.notify.success(libsUtil.PRIVACY_MESSAGE, TIMEOUTS.LONG);
}
// Computed property to get unit options
get unitOptions() {
return this.libsUtil.UNIT_SHORT;
}
/**
* Computed property for current giver entity data
*/
get currentGiver() {
if (this.providerProjectId) {
return {
handleId: this.providerProjectId,
name: this.providerProjectName,
};
} else if (this.giverDid) {
return {
did: this.giverDid,
name: this.giverName,
};
}
return undefined;
}
/**
* Computed property for current receiver entity data
*/
get currentReceiver() {
if (this.givenToProject() && this.fulfillsProjectId) {
return {
handleId: this.fulfillsProjectId,
name: this.fulfillsProjectName,
};
} else if (this.givenToPerson() && this.recipientDid) {
return {
did: this.recipientDid,
name: this.recipientName,
};
}
return undefined;
}
/**
* Open giver selection interface
*/
openGiverSelection() {
this.showGiverSelection = true;
}
/**
* Close giver selection interface
*/
closeGiverSelection() {
this.showGiverSelection = false;
}
/**
* Handle giver entity selection
*/
handleGiverEntitySelected(entity: {
type: "person" | "project";
data: Contact | PlanData;
stepType: "giver" | "recipient";
}) {
if (entity.type === "person") {
const contact = entity.data as Contact;
this.giverDid = contact.did;
this.giverName = contact.name || "";
this.providerProjectId = "";
} else if (entity.type === "project") {
const project = entity.data as PlanData;
this.providerProjectId = project.handleId || "";
this.providerProjectName = project.name
? `the project "${project.name}"`
: "a project";
this.giverDid = "";
}
this.showGiverSelection = false;
}
/**
* Open recipient selection interface
*/
openRecipientSelection() {
this.showRecipientSelection = true;
}
/**
* Close recipient selection interface
*/
closeRecipientSelection() {
this.showRecipientSelection = false;
}
/**
* Handle recipient entity selection
*/
handleRecipientEntitySelected(entity: {
type: "person" | "project";
data: Contact | PlanData;
stepType: "giver" | "recipient";
}) {
if (entity.type === "person") {
const contact = entity.data as Contact;
this.recipientDid = contact.did;
this.recipientName = contact.name || "";
this.fulfillsProjectId = "";
} else if (entity.type === "project") {
const project = entity.data as PlanData;
this.fulfillsProjectId = project.handleId || "";
this.fulfillsProjectName = project.name
? `the project "${project.name}"`
: "a project";
this.recipientDid = "";
}
this.showRecipientSelection = false;
}
/**
* Check if selecting an entity would create a conflict
*/
wouldCreateConflictWithGiver(identifier: string): boolean {
// Check if it would conflict with giver
if (this.giverDid === identifier) {
return true;
}
if (this.providerProjectId === identifier) {
return true;
}
return false;
}
wouldCreateConflictWithRecipient(identifier: string): boolean {
// Check if it would conflict with recipient
if (this.recipientDid === identifier) {
return true;
}
if (this.fulfillsProjectId === identifier) {
return true;
}
return false;
}
}
</script>