change the "give" action on contact page to use dialog box

This commit is contained in:
2024-04-21 16:42:22 -06:00
parent ef95708d02
commit 4e877c15f6
17 changed files with 209 additions and 251 deletions

View File

@@ -43,6 +43,7 @@
<div class="flex justify-between" v-if="showGiveNumbers">
<div class="w-full text-right">
<!--
Hours to Add:
<input
class="border rounded border-slate-400 w-24 text-right"
@@ -57,30 +58,29 @@
placeholder="Description"
v-model="hourDescriptionInput"
/>
<br />
<br />
<button
href=""
class="text-center text-md text-white px-1.5 py-2 rounded-md mb-6"
v-bind:class="showGiveAmountsClassNames()"
@click="toggleShowGiveTotals()"
>
{{
showGiveTotals
? "Total"
: showGiveConfirmed
? "Confirmed"
: "Unconfirmed"
}}
</button>
<br />
(Only most recent hours included. To see more, click
-->
In the following, only the most recent hours are included. To see more,
click
<span
class="text-sm 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 py-1 rounded-md"
>
<fa icon="file-lines" class="fa-fw" />
</span>
)
<br />
<button
href=""
class="text-center text-md text-white px-1.5 py-2 rounded-md mt-1"
v-bind:class="showGiveAmountsClassNames()"
@click="toggleShowGiveTotals()"
>
{{
showGiveTotals
? "Showing Total"
: showGiveConfirmed
? "Confirmed"
: "Unconfirmed"
}}
</button>
</div>
</div>
@@ -189,10 +189,11 @@
>
<button
class="text-sm 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-1.5 rounded-l-md"
@click="onClickAddGive(activeDid, contact.did)"
@click="showGiftedDialog(activeDid, contact.did)"
:title="givenByMeDescriptions[contact.did] || ''"
>
To:
<br />
{{
/* eslint-disable prettier/prettier */
this.showGiveTotals
@@ -203,15 +204,17 @@
: (givenByMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */
}}
<br />
<fa icon="plus" />
</button>
<button
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l"
@click="onClickAddGive(contact.did, activeDid)"
@click="showGiftedDialog(contact.did, this.activeDid)"
:title="givenToMeDescriptions[contact.did] || ''"
>
From:
<br />
{{
/* eslint-disable prettier/prettier */
this.showGiveTotals
@@ -222,6 +225,7 @@
: (givenToMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */
}}
<br />
<fa icon="plus" />
</button>
@@ -237,7 +241,7 @@
name: 'contact-amounts',
query: { contactDid: contact.did },
}"
class="text-sm 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-2 py-1.5 rounded-md"
class="text-sm 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-2 py-1.5 rounded-md border border-slate-400"
title="See more given activity"
>
<fa icon="file-lines" class="fa-fw" />
@@ -249,6 +253,7 @@
</ul>
<p v-else>There are no contacts.</p>
<GiftedDialog ref="customGivenDialog" />
<OfferDialog ref="customOfferDialog" />
<div v-if="showLargeIdenticon" class="fixed z-[100] top-0 inset-x-0 w-full">
@@ -313,8 +318,8 @@ import {
import {
CONTACT_CSV_HEADER,
CONTACT_URL_PREFIX,
GiverReceiverInputInfo,
GiveSummaryRecord,
GiveVerifiableCredential,
isDid,
RegisterVerifiableCredential,
SERVICE_ID,
@@ -322,13 +327,14 @@ import {
import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import GiftedDialog from "@/components/GiftedDialog.vue";
import OfferDialog from "@/components/OfferDialog.vue";
import { Account } from "@/db/tables/accounts";
import { Buffer } from "buffer/";
@Component({
components: { EntityIcon, OfferDialog, QuickNav },
components: { GiftedDialog, EntityIcon, OfferDialog, QuickNav },
})
export default class ContactsView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
@@ -352,8 +358,6 @@ export default class ContactsView extends Vue {
givenToMeConfirmed: Record<string, number> = {};
// { "did:...": amount } entry for each contact
givenToMeUnconfirmed: Record<string, number> = {};
hourDescriptionInput = "";
hourInput = "0";
isRegistered = false;
showDidCopy = false;
showGiveNumbers = false;
@@ -1041,27 +1045,33 @@ export default class ContactsView extends Vue {
}
private nameForDid(contacts: Array<Contact>, did: string): string {
if (did === this.activeDid) {
return "you";
}
const contact = R.find((con) => con.did == did, contacts);
return this.nameForContact(contact);
}
private nameForContact(contact?: Contact, capitalize?: boolean): string {
return contact?.name || (capitalize ? "T" : "t") + "his unnamed user";
return (
(contact?.name as string) || (capitalize ? "T" : "t") + "his unnamed user"
);
}
async onClickAddGive(fromDid: string, toDid: string): Promise<void> {
const identity = await this.getIdentity(this.activeDid);
private showGiftedDialog(giverDid: string, recipientDid: string) {
// if they have unconfirmed amounts, ask to confirm those first
if (toDid == identity?.did && this.givenToMeUnconfirmed[fromDid] > 0) {
const isare = this.givenToMeUnconfirmed[fromDid] == 1 ? "is" : "are";
const hours = this.givenToMeUnconfirmed[fromDid] == 1 ? "hour" : "hours";
if (
recipientDid == this.activeDid &&
this.givenToMeUnconfirmed[giverDid] > 0
) {
const isAre = this.givenToMeUnconfirmed[giverDid] == 1 ? "is" : "are";
const hours = this.givenToMeUnconfirmed[giverDid] == 1 ? "hour" : "hours";
if (
confirm(
"There " +
isare +
isAre +
" " +
this.givenToMeUnconfirmed[fromDid] +
this.givenToMeUnconfirmed[giverDid] +
" unconfirmed " +
hours +
" from them." +
@@ -1070,178 +1080,58 @@ export default class ContactsView extends Vue {
) {
this.$router.push({
name: "contact-amounts",
query: { contactDid: fromDid },
query: { contactDid: giverDid },
});
return;
}
}
if (!libsUtil.isNumeric(this.hourInput)) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Input Error",
text: "This is not a valid number of hours: " + this.hourInput,
},
3000,
);
} else if (parseFloat(this.hourInput) == 0 && !this.hourDescriptionInput) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Input Error",
text: "Giving no hours or description does nothing.",
},
3000,
);
} else if (!identity) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Status Error",
text: "No identifier is available.",
},
3000,
);
} else {
// ask to confirm amount
let toFrom;
if (fromDid == identity?.did) {
toFrom = "from you to " + this.nameForDid(this.contacts, toDid);
} else {
toFrom = "from " + this.nameForDid(this.contacts, fromDid) + " to you";
}
let description;
if (this.hourDescriptionInput) {
description = " with description '" + this.hourDescriptionInput + "'";
} else {
description = " with no description";
}
if (
confirm(
"Are you sure you want to record " +
this.hourInput +
" hour" +
(this.hourInput == "1" ? "" : "s") +
" " +
toFrom +
description +
"?",
)
) {
this.createAndSubmitContactGive(
identity,
fromDid,
toDid,
parseFloat(this.hourInput),
this.hourDescriptionInput,
);
}
let giver: GiverReceiverInputInfo, receiver: GiverReceiverInputInfo;
if (giverDid) {
giver = {
did: giverDid,
name: this.nameForDid(this.contacts, giverDid),
};
}
if (recipientDid) {
receiver = {
did: recipientDid,
name: this.nameForDid(this.contacts, recipientDid),
};
}
let callback: (amount: number) => void;
let customTitle = "Given";
// choose whether to open dialog to user or from user
if (giverDid == this.activeDid) {
callback = (amount: number) => {
const newList = R.clone(this.givenByMeUnconfirmed);
newList[recipientDid] = (newList[recipientDid] || 0) + amount;
this.givenByMeUnconfirmed = newList;
};
customTitle = "To " + receiver.name;
} else {
// must be (recipientDid == this.activeDid)
callback = (amount: number) => {
const newList = R.clone(this.givenToMeUnconfirmed);
newList[giverDid] = (newList[giverDid] || 0) + amount;
this.givenToMeUnconfirmed = newList;
};
customTitle = "From " + giver.name;
}
(this.$refs.customGivenDialog as GiftedDialog).open(
giver,
receiver,
undefined as string,
customTitle,
callback,
);
}
openOfferDialog(recipientDid: string) {
(this.$refs.customOfferDialog as OfferDialog).open(recipientDid);
}
// similar function is in endorserServer.ts
private async createAndSubmitContactGive(
identity: IIdentifier,
fromDid: string,
toDid: string,
amount: number,
description: string,
): Promise<void> {
// Make a claim
const vcClaim: GiveVerifiableCredential = {
"@context": "https://schema.org",
"@type": "GiveAction",
agent: { identifier: fromDid },
object: { amountOfThisGood: amount, unitCode: "HUR" },
recipient: { identifier: toDid },
};
if (description) {
vcClaim.description = description;
}
// Make a payload for the claim
const vcPayload = {
vc: {
"@context": ["https://www.w3.org/2018/credentials/v1"],
type: ["VerifiableCredential"],
credentialSubject: vcClaim,
},
};
// Create a signature using private key of identity
if (identity.keys[0].privateKeyHex !== null) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const privateKeyHex: string = identity.keys[0].privateKeyHex!;
const signer = await SimpleSigner(privateKeyHex);
const alg = undefined;
// Create a JWT for the request
const vcJwt: string = await didJwt.createJWT(vcPayload, {
alg: alg,
issuer: identity.did,
signer: signer,
});
// Make the xhr request payload
const payload = JSON.stringify({ jwtEncoded: vcJwt });
const url = this.apiServer + "/api/v2/claim";
const headers = await this.getHeaders(identity);
try {
const resp = await this.axios.post(url, payload, { headers });
if (resp.data?.success?.handleId) {
this.$notify(
{
group: "alert",
type: "success",
title: "Done",
text: "Successfully logged time to the server.",
},
5000,
);
if (fromDid === identity.did) {
const newList = R.clone(this.givenByMeUnconfirmed);
newList[toDid] = (newList[toDid] || 0) + amount;
this.givenByMeUnconfirmed = newList;
} else {
const newList = R.clone(this.givenToMeConfirmed);
newList[fromDid] = (newList[fromDid] || 0) + amount;
this.givenToMeConfirmed = newList;
}
}
} catch (error) {
console.error("Error in createAndSubmitContactGive: ", error);
let userMessage = "There was an error. See logs for more info.";
const serverError = error as AxiosError;
if (serverError) {
if (serverError.message) {
userMessage = serverError.message; // Info for the user
} else {
userMessage = JSON.stringify(serverError.toJSON());
}
} else {
userMessage = error as string;
}
// Now set that error for the user to see.
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Sending Give",
text: userMessage,
},
5000,
);
}
}
}
private async onClickCancelName() {
this.contactEdit = null;
this.contactNewName = "";