|
|
@ -1,99 +1,477 @@ |
|
|
|
<template> |
|
|
|
<div v-if="visible" class="dialog-overlay"> |
|
|
|
<div class="dialog"> |
|
|
|
<h1 class="text-xl font-bold text-center mb-4"> |
|
|
|
{{ customTitle }} |
|
|
|
</h1> |
|
|
|
<input |
|
|
|
v-model="description" |
|
|
|
type="text" |
|
|
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2" |
|
|
|
:placeholder="prompt || 'What was given?'" |
|
|
|
/> |
|
|
|
<div class="flex flex-row justify-center"> |
|
|
|
<span |
|
|
|
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center text-blue-500 px-2 py-2 w-20" |
|
|
|
@click="changeUnitCode()" |
|
|
|
<!-- Step 1: Giver --> |
|
|
|
<div v-show="currentStep === 1" 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' |
|
|
|
" |
|
|
|
> |
|
|
|
{{ libsUtil.UNIT_SHORT[unitCode] || unitCode }} |
|
|
|
</span> |
|
|
|
<div |
|
|
|
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2" |
|
|
|
@click="amountInput === '0' ? null : decrement()" |
|
|
|
<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-500 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-500 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), |
|
|
|
'text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden text-slate-400': wouldCreateConflict(contact.did) |
|
|
|
}" |
|
|
|
> |
|
|
|
{{ contact.name || contact.did }} |
|
|
|
</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-500 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" |
|
|
|
> |
|
|
|
<font-awesome icon="chevron-left" /> |
|
|
|
Cancel |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- Step 2: Gift --> |
|
|
|
<div v-show="currentStep === 2" 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 class="font-semibold truncate"> |
|
|
|
{{ giver?.name || "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 class="font-semibold truncate"> |
|
|
|
{{ giver?.name || "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 class="font-semibold truncate"> |
|
|
|
{{ receiver?.name || "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 class="font-semibold truncate"> |
|
|
|
{{ receiver?.name || "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 |
|
|
|
id="inputGivenAmount" |
|
|
|
v-model="amountInput" |
|
|
|
type="number" |
|
|
|
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20" |
|
|
|
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="rounded-r border border-slate-400 bg-slate-200 px-4 py-2" |
|
|
|
@click="increment()" |
|
|
|
<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 value="HUR">Hours</option> |
|
|
|
<option value="USD">US $</option> |
|
|
|
<option value="BTC">BTC</option> |
|
|
|
<option value="BX">BX</option> |
|
|
|
<option value="ETH">ETH</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" |
|
|
|
> |
|
|
|
<font-awesome icon="chevron-right" /> |
|
|
|
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> |
|
|
|
<div class="mt-4 flex justify-center"> |
|
|
|
<span> |
|
|
|
<router-link |
|
|
|
:to="{ |
|
|
|
name: 'gifted-details', |
|
|
|
query: { |
|
|
|
amountInput, |
|
|
|
description, |
|
|
|
giverDid: giver?.did, |
|
|
|
giverName: giver?.name, |
|
|
|
offerId, |
|
|
|
fulfillsProjectId: toProjectId, |
|
|
|
providerProjectId: fromProjectId, |
|
|
|
recipientDid: receiver?.did, |
|
|
|
recipientName: receiver?.name, |
|
|
|
unitCode, |
|
|
|
}, |
|
|
|
|
|
|
|
<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 |
|
|
|
}" |
|
|
|
class="text-blue-500" |
|
|
|
@click="confirm" |
|
|
|
> |
|
|
|
Photo & more options ... |
|
|
|
</router-link> |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
<p class="text-center mb-2 mt-6 italic"> |
|
|
|
Sign & Send to publish to the world |
|
|
|
<font-awesome |
|
|
|
icon="circle-info" |
|
|
|
class="pl-2 text-blue-500 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 & 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> |
|
|
|
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 } from "vue-facing-decorator"; |
|
|
|
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"; |
|
|
@ -102,13 +480,38 @@ 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 |
|
|
|
@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> = []; |
|
|
@ -125,9 +528,84 @@ export default class GiftedDialog extends Vue { |
|
|
|
receiver?: libsUtil.GiverReceiverInputInfo; |
|
|
|
unitCode = "HUR"; |
|
|
|
visible = false; |
|
|
|
currentStep = 1; |
|
|
|
|
|
|
|
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, |
|
|
@ -140,10 +618,14 @@ export default class GiftedDialog extends Vue { |
|
|
|
this.giver = giver; |
|
|
|
this.prompt = prompt || ""; |
|
|
|
this.receiver = receiver; |
|
|
|
// if we show "given to user" selection, default checkbox to true |
|
|
|
this.amountInput = "0"; |
|
|
|
this.callbackOnSuccess = callbackOnSuccess; |
|
|
|
this.offerId = offerId || ""; |
|
|
|
this.currentStep = giver ? 2 : 1; |
|
|
|
this.stepType = "giver"; |
|
|
|
|
|
|
|
// Update entity types based on current props |
|
|
|
this.updateEntityTypes(); |
|
|
|
|
|
|
|
try { |
|
|
|
let settings = await databaseUtil.retrieveSettingsForActiveAccount(); |
|
|
@ -174,7 +656,16 @@ export default class GiftedDialog extends Vue { |
|
|
|
this.allContacts, |
|
|
|
); |
|
|
|
} |
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any |
|
|
|
|
|
|
|
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( |
|
|
@ -224,6 +715,7 @@ export default class GiftedDialog extends Vue { |
|
|
|
this.amountInput = "0"; |
|
|
|
this.prompt = ""; |
|
|
|
this.unitCode = "HUR"; |
|
|
|
this.currentStep = 1; |
|
|
|
} |
|
|
|
|
|
|
|
async confirm() { |
|
|
@ -265,6 +757,20 @@ export default class GiftedDialog extends Vue { |
|
|
|
); |
|
|
|
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( |
|
|
@ -304,20 +810,46 @@ export default class GiftedDialog extends Vue { |
|
|
|
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 { |
|
|
|
// 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, |
|
|
|
giverDid as string, |
|
|
|
recipientDid as string, |
|
|
|
fromDid, |
|
|
|
toDid, |
|
|
|
description, |
|
|
|
amount, |
|
|
|
unitCode, |
|
|
|
this.toProjectId, |
|
|
|
fulfillsProjectHandleId, |
|
|
|
this.offerId, |
|
|
|
false, |
|
|
|
undefined, |
|
|
|
this.fromProjectId, |
|
|
|
providerPlanHandleId, |
|
|
|
); |
|
|
|
|
|
|
|
if (!result.success) { |
|
|
@ -391,6 +923,114 @@ export default class GiftedDialog extends Vue { |
|
|
|
-1, |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
selectGiver(contact?: Contact) { |
|
|
|
if (contact) { |
|
|
|
this.giver = { |
|
|
|
did: contact.did, |
|
|
|
name: contact.name || contact.did, |
|
|
|
}; |
|
|
|
} else { |
|
|
|
this.giver = { |
|
|
|
did: "", |
|
|
|
name: "Unnamed", |
|
|
|
}; |
|
|
|
} |
|
|
|
this.currentStep = 2; |
|
|
|
} |
|
|
|
|
|
|
|
goBackToStep1(step: string) { |
|
|
|
this.stepType = step; |
|
|
|
this.currentStep = 1; |
|
|
|
} |
|
|
|
|
|
|
|
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.currentStep = 2; |
|
|
|
} |
|
|
|
|
|
|
|
selectRecipient(contact?: Contact) { |
|
|
|
if (contact) { |
|
|
|
this.receiver = { |
|
|
|
did: contact.did, |
|
|
|
name: contact.name || contact.did, |
|
|
|
}; |
|
|
|
} else { |
|
|
|
this.receiver = { |
|
|
|
did: "", |
|
|
|
name: "Unnamed", |
|
|
|
}; |
|
|
|
} |
|
|
|
this.currentStep = 2; |
|
|
|
} |
|
|
|
|
|
|
|
selectRecipientProject(project: PlanData) { |
|
|
|
this.receiver = { |
|
|
|
did: project.handleId, |
|
|
|
name: project.name, |
|
|
|
image: project.image, |
|
|
|
handleId: project.handleId, |
|
|
|
}; |
|
|
|
this.currentStep = 2; |
|
|
|
} |
|
|
|
|
|
|
|
// 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, |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |
|
|
|
|
|
|
|