You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1114 lines
35 KiB
1114 lines
35 KiB
<template>
|
|
<div v-if="visible" class="dialog-overlay">
|
|
<div class="dialog">
|
|
<!-- Step 1: Giver -->
|
|
<div v-show="firstStep" id="sectionGiftedGiver">
|
|
<label class="block font-bold mb-4">
|
|
{{
|
|
stepType === "recipient"
|
|
? "Choose who received the gift:"
|
|
: showProjects
|
|
? "Choose a project benefitted from:"
|
|
: "Choose a person received from:"
|
|
}}
|
|
</label>
|
|
|
|
<!-- Unified Quick-pick grid for People and Projects -->
|
|
<ul
|
|
:class="
|
|
shouldShowProjects
|
|
? 'grid grid-cols-3 md:grid-cols-4 gap-x-2 gap-y-4 text-center mb-4'
|
|
: 'grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-x-2 gap-y-4 text-center mb-4'
|
|
"
|
|
>
|
|
<template v-if="shouldShowProjects">
|
|
<!-- show projects -->
|
|
<li
|
|
v-for="project in projects.slice(0, 7)"
|
|
:key="project.handleId"
|
|
class="cursor-pointer"
|
|
@click="
|
|
stepType === 'recipient'
|
|
? selectRecipientProject(project)
|
|
: selectProject(project)
|
|
"
|
|
>
|
|
<div class="relative w-fit mx-auto">
|
|
<ProjectIcon
|
|
:entity-id="project.handleId"
|
|
:icon-size="48"
|
|
:image-url="project.image"
|
|
class="!size-[3rem] mx-auto border border-slate-300 bg-white overflow-hidden rounded-full mb-1"
|
|
/>
|
|
</div>
|
|
<h3
|
|
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
|
|
>
|
|
{{ project.name }}
|
|
</h3>
|
|
<div class="text-xs text-slate-500 truncate">
|
|
<font-awesome icon="user" class="fa-fw text-slate-400" />
|
|
{{
|
|
didInfo(project.issuerDid, activeDid, allMyDids, allContacts)
|
|
}}
|
|
</div>
|
|
</li>
|
|
<li
|
|
v-if="projects.length === 0"
|
|
class="text-xs text-slate-500 italic col-span-full"
|
|
>
|
|
(No projects found.)
|
|
</li>
|
|
<li v-if="projects.length > 0">
|
|
<router-link :to="{ name: 'discover' }" class="cursor-pointer">
|
|
<font-awesome
|
|
icon="circle-right"
|
|
class="text-blue-500 text-5xl mb-1"
|
|
/>
|
|
<h3
|
|
class="text-xs text-slate-400 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden"
|
|
>
|
|
Show All
|
|
</h3>
|
|
</router-link>
|
|
</li>
|
|
</template>
|
|
<template v-else>
|
|
<!-- show people (contacts) -->
|
|
<li
|
|
v-if="
|
|
stepType === 'recipient' ||
|
|
(stepType === 'giver' && isFromProjectView)
|
|
"
|
|
:class="{
|
|
'cursor-pointer': !wouldCreateConflict(activeDid),
|
|
'cursor-not-allowed opacity-50': wouldCreateConflict(activeDid)
|
|
}"
|
|
@click="
|
|
!wouldCreateConflict(activeDid) &&
|
|
(stepType === 'recipient'
|
|
? selectRecipient({ did: activeDid, name: 'You' })
|
|
: selectGiver({ did: activeDid, name: 'You' }))
|
|
"
|
|
>
|
|
<font-awesome
|
|
:class="{
|
|
'text-blue-500 text-5xl mb-1': !wouldCreateConflict(activeDid),
|
|
'text-slate-400 text-5xl mb-1': wouldCreateConflict(activeDid)
|
|
}"
|
|
icon="hand"
|
|
/>
|
|
<h3
|
|
:class="{
|
|
'text-xs text-blue-500 font-medium text-ellipsis whitespace-nowrap overflow-hidden': !wouldCreateConflict(activeDid),
|
|
'text-xs text-slate-400 font-medium text-ellipsis whitespace-nowrap overflow-hidden': wouldCreateConflict(activeDid)
|
|
}"
|
|
>
|
|
You
|
|
</h3>
|
|
</li>
|
|
<li
|
|
class="cursor-pointer"
|
|
@click="
|
|
stepType === 'recipient' ? selectRecipient() : selectGiver()
|
|
"
|
|
>
|
|
<font-awesome
|
|
icon="circle-question"
|
|
class="text-slate-400 text-5xl mb-1"
|
|
/>
|
|
<h3
|
|
class="text-xs text-slate-400 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden"
|
|
>
|
|
(Unnamed)
|
|
</h3>
|
|
</li>
|
|
<li
|
|
v-if="allContacts.length === 0"
|
|
class="text-xs text-slate-500 italic col-span-full"
|
|
>
|
|
(Add friends to see more people worthy of recognition.)
|
|
</li>
|
|
<li
|
|
v-for="contact in allContacts.slice(0, 10)"
|
|
:key="contact.did"
|
|
:class="{
|
|
'cursor-pointer': !wouldCreateConflict(contact.did),
|
|
'cursor-not-allowed opacity-50': wouldCreateConflict(contact.did)
|
|
}"
|
|
@click="
|
|
!wouldCreateConflict(contact.did) &&
|
|
(stepType === 'recipient'
|
|
? selectRecipient(contact)
|
|
: selectGiver(contact))
|
|
"
|
|
>
|
|
<div class="relative w-fit mx-auto">
|
|
<EntityIcon
|
|
:contact="contact"
|
|
class="!size-[3rem] mx-auto border border-slate-300 bg-white overflow-hidden rounded-full mb-1"
|
|
/>
|
|
<div
|
|
class="rounded-full bg-slate-400 absolute bottom-0 right-0 p-1 translate-x-1/3"
|
|
>
|
|
<font-awesome
|
|
icon="clock"
|
|
class="block text-white text-xs w-[1em]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<h3
|
|
:class="{
|
|
'text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden': !wouldCreateConflict(contact.did) && contact.name,
|
|
'text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden text-slate-400 italic': !contact.name,
|
|
'text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden text-slate-400': wouldCreateConflict(contact.did) && contact.name
|
|
}"
|
|
>
|
|
{{ contact.name || "(No name)" }}
|
|
</h3>
|
|
</li>
|
|
<li v-if="allContacts.length > 0" class="cursor-pointer">
|
|
<router-link
|
|
:to="{
|
|
name: 'contact-gift',
|
|
query: {
|
|
stepType: stepType,
|
|
giverEntityType: giverEntityType,
|
|
recipientEntityType: recipientEntityType,
|
|
...(stepType === 'giver'
|
|
? {
|
|
recipientProjectId: toProjectId,
|
|
recipientProjectName: receiver?.name,
|
|
recipientProjectImage: receiver?.image,
|
|
recipientProjectHandleId: receiver?.handleId,
|
|
recipientDid: receiver?.did,
|
|
}
|
|
: {
|
|
giverProjectId: fromProjectId,
|
|
giverProjectName: giver?.name,
|
|
giverProjectImage: giver?.image,
|
|
giverProjectHandleId: giver?.handleId,
|
|
giverDid: giver?.did,
|
|
}),
|
|
fromProjectId: fromProjectId,
|
|
toProjectId: toProjectId,
|
|
showProjects: (showProjects || false).toString(),
|
|
isFromProjectView: (isFromProjectView || false).toString(),
|
|
},
|
|
}"
|
|
>
|
|
<font-awesome
|
|
icon="circle-right"
|
|
class="text-blue-500 text-5xl mb-1"
|
|
/>
|
|
<h3
|
|
class="text-xs text-slate-400 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden"
|
|
>
|
|
Show All
|
|
</h3>
|
|
</router-link>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
|
|
<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-lg"
|
|
@click="cancel"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Step 2: Gift -->
|
|
<div v-show="!firstStep" id="sectionGiftedGift">
|
|
<div class="grid grid-cols-2 gap-2 mb-4">
|
|
<!-- Giver Button -->
|
|
<button
|
|
v-if="
|
|
(giverEntityType === 'person' || giverEntityType === 'project') &&
|
|
!(isFromProjectView && giverEntityType === 'project')
|
|
"
|
|
class="flex-1 flex items-center gap-2 bg-slate-100 border border-slate-300 rounded-md p-2"
|
|
@click="goBackToStep1('giver')"
|
|
>
|
|
<div>
|
|
<template v-if="giverEntityType === 'project'">
|
|
<ProjectIcon
|
|
v-if="giver?.handleId"
|
|
:entity-id="giver.handleId"
|
|
:icon-size="32"
|
|
:image-url="giver.image"
|
|
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
/>
|
|
</template>
|
|
<template v-else>
|
|
<EntityIcon
|
|
v-if="giver?.did"
|
|
:contact="giver"
|
|
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
/>
|
|
<font-awesome
|
|
v-else
|
|
icon="circle-question"
|
|
class="text-slate-400 text-3xl"
|
|
/>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="text-start min-w-0">
|
|
<p class="text-xs text-slate-500 leading-1 -mb-1 uppercase">
|
|
{{
|
|
giverEntityType === "project"
|
|
? "Benefited from:"
|
|
: "Received from:"
|
|
}}
|
|
</p>
|
|
<h3
|
|
v-if="giver?.name && giver.name !== giver.did"
|
|
class="font-semibold truncate"
|
|
>
|
|
{{ giver.name }}
|
|
</h3>
|
|
<h3
|
|
v-if="giver?.name && giver.name === giver.did"
|
|
class="font-semibold truncate text-slate-400 italic"
|
|
>
|
|
(No name)
|
|
</h3>
|
|
<h3
|
|
v-else-if="!giver?.name"
|
|
class="font-semibold truncate text-slate-400 italic"
|
|
>
|
|
(Unnamed)
|
|
</h3>
|
|
</div>
|
|
|
|
<p class="ms-auto text-sm text-blue-500 pe-1">
|
|
<font-awesome icon="pen" title="Change" />
|
|
</p>
|
|
</button>
|
|
<div
|
|
v-else
|
|
class="flex-1 flex items-center gap-2 bg-slate-100 border border-slate-300 rounded-md p-2"
|
|
>
|
|
<div>
|
|
<template v-if="giverEntityType === 'project'">
|
|
<ProjectIcon
|
|
v-if="giver?.handleId"
|
|
:entity-id="giver.handleId"
|
|
:icon-size="32"
|
|
:image-url="giver.image"
|
|
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
/>
|
|
</template>
|
|
<template v-else>
|
|
<EntityIcon
|
|
v-if="giver?.did"
|
|
:contact="giver"
|
|
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
/>
|
|
<font-awesome
|
|
v-else
|
|
icon="circle-question"
|
|
class="text-slate-400 text-3xl"
|
|
/>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="text-start min-w-0">
|
|
<p class="text-xs text-slate-500 leading-1 -mb-1 uppercase">
|
|
{{
|
|
giverEntityType === "project"
|
|
? "Benefited from:"
|
|
: "Received from:"
|
|
}}
|
|
</p>
|
|
<h3
|
|
v-if="giver?.name && giver.name !== giver.did"
|
|
class="font-semibold truncate"
|
|
>
|
|
{{ giver.name }}
|
|
</h3>
|
|
<h3
|
|
v-if="giver?.name && giver.name === giver.did"
|
|
class="font-semibold truncate text-slate-400 italic"
|
|
>
|
|
(No name)
|
|
</h3>
|
|
<h3
|
|
v-else-if="!giver?.name"
|
|
class="font-semibold truncate text-slate-400 italic"
|
|
>
|
|
(Unnamed)
|
|
</h3>
|
|
</div>
|
|
|
|
<p class="ms-auto text-sm text-slate-400 pe-1">
|
|
<font-awesome icon="lock" title="Can't be changed" />
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Recipient Button -->
|
|
<button
|
|
v-if="recipientEntityType === 'person'"
|
|
class="flex-1 flex items-center gap-2 bg-slate-100 border border-slate-300 rounded-md p-2"
|
|
@click="goBackToStep1('recipient')"
|
|
>
|
|
<div>
|
|
<EntityIcon
|
|
v-if="receiver?.did"
|
|
:contact="receiver"
|
|
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
/>
|
|
<font-awesome
|
|
v-else
|
|
icon="circle-question"
|
|
class="text-slate-400 text-3xl"
|
|
/>
|
|
</div>
|
|
|
|
<div class="text-start min-w-0">
|
|
<p class="text-xs text-slate-500 leading-1 -mb-1 uppercase">
|
|
Given to:
|
|
</p>
|
|
<h3
|
|
v-if="receiver?.name && receiver.name !== receiver.did"
|
|
class="font-semibold truncate"
|
|
>
|
|
{{ receiver.name }}
|
|
</h3>
|
|
<h3
|
|
v-if="receiver?.name && receiver.name === receiver.did"
|
|
class="font-semibold truncate text-slate-400 italic"
|
|
>
|
|
(No name)
|
|
</h3>
|
|
<h3
|
|
v-else-if="!receiver?.name"
|
|
class="font-semibold truncate text-slate-400 italic"
|
|
>
|
|
(Unnamed)
|
|
</h3>
|
|
</div>
|
|
|
|
<p class="ms-auto text-sm text-blue-500 pe-1">
|
|
<font-awesome icon="pen" title="Change" />
|
|
</p>
|
|
</button>
|
|
<div
|
|
v-else-if="recipientEntityType === 'project'"
|
|
class="flex-1 flex items-center gap-2 bg-slate-100 border border-slate-300 rounded-md p-2"
|
|
>
|
|
<div>
|
|
<ProjectIcon
|
|
v-if="receiver?.handleId"
|
|
:entity-id="receiver.handleId"
|
|
:icon-size="32"
|
|
:image-url="receiver.image"
|
|
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
/>
|
|
</div>
|
|
|
|
<div class="text-start min-w-0">
|
|
<p class="text-xs text-slate-500 leading-1 -mb-1 uppercase">
|
|
Given to project:
|
|
</p>
|
|
<h3
|
|
v-if="receiver?.name"
|
|
class="font-semibold truncate"
|
|
>
|
|
{{ receiver.name }}
|
|
</h3>
|
|
<h3
|
|
v-else
|
|
class="font-semibold truncate text-slate-400 italic"
|
|
>
|
|
(Unnamed)
|
|
</h3>
|
|
</div>
|
|
|
|
<p class="ms-auto text-sm text-slate-400 pe-1">
|
|
<font-awesome icon="lock" title="Can't be changed" />
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<input
|
|
v-model="description"
|
|
type="text"
|
|
class="block w-full rounded border border-slate-400 px-3 py-2 mb-4 placeholder:italic"
|
|
:placeholder="prompt || 'What was given?'"
|
|
/>
|
|
<div class="flex mb-4">
|
|
<button
|
|
class="rounded-s border border-e-0 border-slate-400 bg-slate-200 px-4 py-2"
|
|
@click="amountInput === '0' ? null : decrement()"
|
|
>
|
|
<font-awesome icon="chevron-left" />
|
|
</button>
|
|
<input
|
|
id="inputGivenAmount"
|
|
v-model="amountInput"
|
|
type="number"
|
|
class="flex-1 border border-e-0 border-slate-400 px-2 py-2 text-center w-[1px]"
|
|
/>
|
|
<button
|
|
class="rounded-e border border-slate-400 bg-slate-200 px-4 py-2"
|
|
@click="increment()"
|
|
>
|
|
<font-awesome icon="chevron-right" />
|
|
</button>
|
|
|
|
<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>
|
|
<router-link
|
|
:to="{
|
|
name: 'gifted-details',
|
|
query: giftedDetailsQuery,
|
|
}"
|
|
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-lg mb-4"
|
|
>
|
|
Photo & more options…
|
|
</router-link>
|
|
<p class="text-center text-sm mb-4">
|
|
<b class="font-medium">Sign & 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>
|
|
|
|
<!-- Conflict warning -->
|
|
<div v-if="hasPersonConflict" class="mb-4 p-3 bg-red-50 border border-red-200 rounded-md">
|
|
<p class="text-red-700 text-sm text-center">
|
|
<font-awesome icon="exclamation-triangle" class="fa-fw mr-1" />
|
|
Cannot record: Same person selected as both giver and recipient
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
|
<button
|
|
:disabled="hasPersonConflict"
|
|
:class="{
|
|
'block w-full text-center text-md uppercase font-bold 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-1.5 py-2 rounded-lg': !hasPersonConflict,
|
|
'block w-full text-center text-md uppercase font-bold bg-gradient-to-b from-slate-300 to-slate-500 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-slate-400 px-1.5 py-2 rounded-lg cursor-not-allowed': hasPersonConflict
|
|
}"
|
|
@click="confirm"
|
|
>
|
|
Sign & 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-lg"
|
|
@click="cancel"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Vue, Component, Prop, Watch } from "vue-facing-decorator";
|
|
|
|
import { NotificationIface, USE_DEXIE_DB } from "../constants/app";
|
|
import {
|
|
createAndSubmitGive,
|
|
didInfo,
|
|
serverMessageForUser,
|
|
getHeaders,
|
|
} from "../libs/endorserServer";
|
|
import * as libsUtil from "../libs/util";
|
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
|
import { Contact } from "../db/tables/contacts";
|
|
import * as databaseUtil from "../db/databaseUtil";
|
|
import { retrieveAccountDids } from "../libs/util";
|
|
import { logger } from "../utils/logger";
|
|
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
|
import EntityIcon from "../components/EntityIcon.vue";
|
|
import ProjectIcon from "../components/ProjectIcon.vue";
|
|
import { PlanData } from "../interfaces/records";
|
|
|
|
@Component({
|
|
components: {
|
|
EntityIcon,
|
|
ProjectIcon,
|
|
},
|
|
})
|
|
export default class GiftedDialog extends Vue {
|
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
|
|
|
@Prop() fromProjectId = "";
|
|
@Prop() toProjectId = "";
|
|
@Prop({ default: false }) showProjects = false;
|
|
@Prop() isFromProjectView = false;
|
|
|
|
@Watch("showProjects")
|
|
onShowProjectsChange() {
|
|
this.updateEntityTypes();
|
|
}
|
|
|
|
@Watch("fromProjectId")
|
|
onFromProjectIdChange() {
|
|
this.updateEntityTypes();
|
|
}
|
|
|
|
@Watch("toProjectId")
|
|
onToProjectIdChange() {
|
|
this.updateEntityTypes();
|
|
}
|
|
|
|
activeDid = "";
|
|
allContacts: Array<Contact> = [];
|
|
allMyDids: Array<string> = [];
|
|
apiServer = "";
|
|
|
|
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 = "";
|
|
prompt = "";
|
|
receiver?: libsUtil.GiverReceiverInputInfo;
|
|
unitCode = "HUR";
|
|
visible = false;
|
|
|
|
libsUtil = libsUtil;
|
|
|
|
projects: PlanData[] = [];
|
|
|
|
didInfo = didInfo;
|
|
|
|
// Computed property to help debug template logic
|
|
get shouldShowProjects() {
|
|
const result =
|
|
(this.stepType === "giver" && this.giverEntityType === "project") ||
|
|
(this.stepType === "recipient" && this.recipientEntityType === "project");
|
|
return result;
|
|
}
|
|
|
|
// Computed property to check if current selection would create a conflict
|
|
get hasPersonConflict() {
|
|
// Only check for conflicts when both entities are persons
|
|
if (this.giverEntityType !== "person" || this.recipientEntityType !== "person") {
|
|
return false;
|
|
}
|
|
|
|
// Check if giver and recipient are the same person
|
|
if (this.giver?.did && this.receiver?.did && this.giver.did === this.receiver.did) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Computed property to check if a contact would create a conflict when selected
|
|
wouldCreateConflict(contactDid: string) {
|
|
// Only check for conflicts when both entities are persons
|
|
if (this.giverEntityType !== "person" || this.recipientEntityType !== "person") {
|
|
return false;
|
|
}
|
|
|
|
if (this.stepType === "giver") {
|
|
// If selecting as giver, check if it conflicts with current recipient
|
|
return this.receiver?.did === contactDid;
|
|
} else if (this.stepType === "recipient") {
|
|
// If selecting as recipient, check if it conflicts with current giver
|
|
return this.giver?.did === contactDid;
|
|
}
|
|
|
|
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,
|
|
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.firstStep = !giver;
|
|
this.stepType = "giver";
|
|
|
|
// Update entity types based on current props
|
|
this.updateEntityTypes();
|
|
|
|
try {
|
|
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
|
if (USE_DEXIE_DB) {
|
|
settings = await retrieveSettingsForActiveAccount();
|
|
}
|
|
this.apiServer = settings.apiServer || "";
|
|
this.activeDid = settings.activeDid || "";
|
|
|
|
const platformService = PlatformServiceFactory.getInstance();
|
|
const result = await platformService.dbQuery(`SELECT * FROM contacts`);
|
|
if (result) {
|
|
this.allContacts = databaseUtil.mapQueryResultToValues(
|
|
result,
|
|
) as unknown as Contact[];
|
|
}
|
|
if (USE_DEXIE_DB) {
|
|
this.allContacts = await db.contacts.toArray();
|
|
}
|
|
|
|
this.allMyDids = await retrieveAccountDids();
|
|
|
|
if (this.giver && !this.giver.name) {
|
|
this.giver.name = didInfo(
|
|
this.giver.did,
|
|
this.activeDid,
|
|
this.allMyDids,
|
|
this.allContacts,
|
|
);
|
|
}
|
|
|
|
if (
|
|
this.giverEntityType === "project" ||
|
|
this.recipientEntityType === "project"
|
|
) {
|
|
await this.loadProjects();
|
|
} else {
|
|
// Clear projects array when not needed
|
|
this.projects = [];
|
|
}
|
|
} catch (err: any) {
|
|
logger.error("Error retrieving settings from database:", err);
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text: err.message || "There was an error retrieving your settings.",
|
|
},
|
|
-1,
|
|
);
|
|
}
|
|
|
|
this.visible = true;
|
|
}
|
|
|
|
close() {
|
|
// close the dialog but don't change values (since it might be submitting info)
|
|
this.visible = false;
|
|
}
|
|
|
|
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,
|
|
)}`;
|
|
}
|
|
|
|
cancel() {
|
|
this.close();
|
|
this.eraseValues();
|
|
}
|
|
|
|
eraseValues() {
|
|
this.description = "";
|
|
this.giver = undefined;
|
|
this.amountInput = "0";
|
|
this.prompt = "";
|
|
this.unitCode = "HUR";
|
|
this.firstStep = true;
|
|
}
|
|
|
|
async confirm() {
|
|
if (!this.activeDid) {
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text: "You must select an identifier before you can record a give.",
|
|
},
|
|
3000,
|
|
);
|
|
return;
|
|
}
|
|
if (parseFloat(this.amountInput) < 0) {
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
text: "You may not send a negative number.",
|
|
title: "",
|
|
},
|
|
2000,
|
|
);
|
|
return;
|
|
}
|
|
if (!this.description && !parseFloat(this.amountInput)) {
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text: `You must enter a description or some number of ${
|
|
this.libsUtil.UNIT_LONG[this.unitCode]
|
|
}.`,
|
|
},
|
|
2000,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Check for person conflict
|
|
if (this.hasPersonConflict) {
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text: "You cannot select the same person as both giver and recipient.",
|
|
},
|
|
3000,
|
|
);
|
|
return;
|
|
}
|
|
|
|
this.close();
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "toast",
|
|
text: "Recording the give...",
|
|
title: "",
|
|
},
|
|
1000,
|
|
);
|
|
// this is asynchronous, but we don't need to wait for it to complete
|
|
await this.recordGive(
|
|
(this.giver?.did as string) || null,
|
|
(this.receiver?.did as string) || null,
|
|
this.description,
|
|
parseFloat(this.amountInput),
|
|
this.unitCode,
|
|
).then(() => {
|
|
this.eraseValues();
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param giverDid may be null
|
|
* @param recipientDid may be null
|
|
* @param description may be an empty string
|
|
* @param amount may be 0
|
|
* @param unitCode may be omitted, defaults to "HUR"
|
|
*/
|
|
async recordGive(
|
|
giverDid: string | null,
|
|
recipientDid: string | null,
|
|
description: string,
|
|
amount: number,
|
|
unitCode: string = "HUR",
|
|
) {
|
|
try {
|
|
// Determine the correct parameters based on entity types
|
|
let fromDid: string | undefined;
|
|
let toDid: string | undefined;
|
|
let fulfillsProjectHandleId: string | undefined;
|
|
let providerPlanHandleId: string | undefined;
|
|
|
|
if (this.giverEntityType === "project" && this.recipientEntityType === "person") {
|
|
// Project-to-person gift
|
|
fromDid = undefined; // No person giver
|
|
toDid = recipientDid as string; // Person recipient
|
|
fulfillsProjectHandleId = undefined; // No project recipient
|
|
providerPlanHandleId = this.giver?.handleId; // Project giver
|
|
} else if (this.giverEntityType === "person" && this.recipientEntityType === "project") {
|
|
// Person-to-project gift
|
|
fromDid = giverDid as string; // Person giver
|
|
toDid = undefined; // No person recipient
|
|
fulfillsProjectHandleId = this.toProjectId; // Project recipient
|
|
providerPlanHandleId = undefined; // No project giver
|
|
} else if (this.giverEntityType === "project" && this.recipientEntityType === "project") {
|
|
// Project-to-project gift
|
|
fromDid = undefined; // No person giver
|
|
toDid = undefined; // No person recipient
|
|
fulfillsProjectHandleId = this.toProjectId; // Project recipient
|
|
providerPlanHandleId = this.giver?.handleId; // Project giver
|
|
} else {
|
|
// Person-to-person gift
|
|
fromDid = giverDid as string;
|
|
toDid = recipientDid as string;
|
|
fulfillsProjectHandleId = undefined;
|
|
providerPlanHandleId = undefined;
|
|
}
|
|
|
|
const result = await createAndSubmitGive(
|
|
this.axios,
|
|
this.apiServer,
|
|
this.activeDid,
|
|
fromDid,
|
|
toDid,
|
|
description,
|
|
amount,
|
|
unitCode,
|
|
fulfillsProjectHandleId,
|
|
this.offerId,
|
|
false,
|
|
undefined,
|
|
providerPlanHandleId,
|
|
);
|
|
|
|
if (!result.success) {
|
|
const errorMessage = result.error;
|
|
logger.error("Error with give creation result:", result);
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text: errorMessage || "There was an error creating the give.",
|
|
},
|
|
-1,
|
|
);
|
|
} else {
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "success",
|
|
title: "Success",
|
|
text: `That gift was recorded.`,
|
|
},
|
|
7000,
|
|
);
|
|
if (this.callbackOnSuccess) {
|
|
this.callbackOnSuccess(amount);
|
|
}
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
} catch (error: any) {
|
|
logger.error("Error with give recordation caught:", error);
|
|
const errorMessage =
|
|
error.userMessage ||
|
|
serverMessageForUser(error) ||
|
|
"There was an error recording the give.";
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text: errorMessage,
|
|
},
|
|
-1,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Helper functions for readability
|
|
|
|
explainData() {
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "success",
|
|
title: "Data Sharing",
|
|
text: libsUtil.PRIVACY_MESSAGE,
|
|
},
|
|
-1,
|
|
);
|
|
}
|
|
|
|
selectGiver(contact?: Contact) {
|
|
if (contact) {
|
|
this.giver = {
|
|
did: contact.did,
|
|
name: contact.name || contact.did,
|
|
};
|
|
} else {
|
|
this.giver = {
|
|
did: "",
|
|
name: "",
|
|
};
|
|
}
|
|
this.firstStep = false;
|
|
}
|
|
|
|
goBackToStep1(step: string) {
|
|
this.stepType = step;
|
|
this.firstStep = true;
|
|
}
|
|
|
|
async loadProjects() {
|
|
try {
|
|
const response = await fetch(this.apiServer + "/api/v2/report/plans", {
|
|
method: "GET",
|
|
headers: await getHeaders(this.activeDid),
|
|
});
|
|
|
|
if (response.status !== 200) {
|
|
throw new Error("Failed to load projects");
|
|
}
|
|
|
|
const results = await response.json();
|
|
if (results.data) {
|
|
this.projects = results.data;
|
|
}
|
|
} catch (error) {
|
|
logger.error("Error loading projects:", error);
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text: "Failed to load projects",
|
|
},
|
|
3000,
|
|
);
|
|
}
|
|
}
|
|
|
|
selectProject(project: PlanData) {
|
|
this.giver = {
|
|
did: project.handleId,
|
|
name: project.name,
|
|
image: project.image,
|
|
handleId: project.handleId,
|
|
};
|
|
this.receiver = {
|
|
did: this.activeDid,
|
|
name: "You",
|
|
};
|
|
this.firstStep = false;
|
|
}
|
|
|
|
selectRecipient(contact?: Contact) {
|
|
if (contact) {
|
|
this.receiver = {
|
|
did: contact.did,
|
|
name: contact.name || contact.did,
|
|
};
|
|
} else {
|
|
this.receiver = {
|
|
did: "",
|
|
name: "",
|
|
};
|
|
}
|
|
this.firstStep = false;
|
|
}
|
|
|
|
selectRecipientProject(project: PlanData) {
|
|
this.receiver = {
|
|
did: project.handleId,
|
|
name: project.name,
|
|
image: project.image,
|
|
handleId: project.handleId,
|
|
};
|
|
this.firstStep = false;
|
|
}
|
|
|
|
// Computed property for the query parameters
|
|
get giftedDetailsQuery() {
|
|
return {
|
|
amountInput: this.amountInput,
|
|
description: this.description,
|
|
giverDid: this.giverEntityType === "person" ? this.giver?.did : undefined,
|
|
giverName: this.giver?.name,
|
|
offerId: this.offerId,
|
|
fulfillsProjectId: this.giverEntityType === "person" && this.recipientEntityType === "project"
|
|
? this.toProjectId
|
|
: undefined,
|
|
providerProjectId: this.giverEntityType === "project" && this.recipientEntityType === "person"
|
|
? this.giver?.handleId
|
|
: this.fromProjectId,
|
|
recipientDid: this.receiver?.did,
|
|
recipientName: this.receiver?.name,
|
|
unitCode: this.unitCode,
|
|
};
|
|
}
|
|
|
|
// Computed property to get unit options
|
|
get unitOptions() {
|
|
return this.libsUtil.UNIT_SHORT;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.dialog-overlay {
|
|
z-index: 50;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.dialog {
|
|
background-color: white;
|
|
padding: 1rem;
|
|
border-radius: 0.5rem;
|
|
width: 100%;
|
|
max-width: 500px;
|
|
}
|
|
</style>
|
|
|