change the "give" action on contact page to use dialog box
This commit is contained in:
@@ -10,6 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Nothing
|
||||
|
||||
|
||||
## [0.3.9] - 2024.04-21
|
||||
### Added
|
||||
- Offer on contacts page
|
||||
### Changed in DB or environment
|
||||
- Nothing
|
||||
|
||||
|
||||
## [0.3.8] - 2024.04-20 - 15c026c80ce03a26cae3ff80b0888934c101c7e2
|
||||
### Added
|
||||
- Profile image for user
|
||||
|
||||
@@ -23,6 +23,10 @@ export default class EntityIcon extends Vue {
|
||||
if (!identifier) {
|
||||
return `<img src="../src/assets/blank-square.svg" class="rounded" width="${this.iconSize}" height="${this.iconSize}" />`;
|
||||
}
|
||||
// https://api.dicebear.com/8.x/avataaars/svg?seed=
|
||||
// ... does not render things with the same seed as this library.
|
||||
// "did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b" yields a girl with flowers in her hair and a lightning earring
|
||||
// ... which looks similar to '' at the dicebear site but which is different.
|
||||
const options: StyleOptions<object> = {
|
||||
seed: (identifier as string) || "",
|
||||
size: this.iconSize,
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<div v-if="visible" class="dialog-overlay">
|
||||
<div class="dialog">
|
||||
<h1 class="text-xl font-bold text-center mb-4">
|
||||
{{ message }} {{ giver?.name || "somebody not named" }}
|
||||
{{ customTitle || message + " " + giver?.name || "somebody not named" }}
|
||||
</h1>
|
||||
<input
|
||||
type="text"
|
||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||
placeholder="What was received"
|
||||
placeholder="What was given"
|
||||
v-model="description"
|
||||
/>
|
||||
<div class="flex flex-row justify-center">
|
||||
@@ -42,12 +42,15 @@
|
||||
name: 'gifted-details',
|
||||
query: {
|
||||
amountInput,
|
||||
customTitle,
|
||||
description,
|
||||
giverDid: giver?.did,
|
||||
giverName: giver?.name,
|
||||
message,
|
||||
offerId,
|
||||
projectId,
|
||||
recipientDid: receiver?.did,
|
||||
recipientName: receiver?.name,
|
||||
unitCode,
|
||||
},
|
||||
}"
|
||||
@@ -90,7 +93,7 @@ import { NotificationIface } from "@/constants/app";
|
||||
import {
|
||||
createAndSubmitGive,
|
||||
didInfo,
|
||||
GiverInputInfo,
|
||||
GiverReceiverInputInfo,
|
||||
} from "@/libs/endorserServer";
|
||||
import * as libsUtil from "@/libs/util";
|
||||
import { accountsDB, db } from "@/db/index";
|
||||
@@ -103,7 +106,6 @@ export default class GiftedDialog extends Vue {
|
||||
|
||||
@Prop message = "";
|
||||
@Prop projectId = "";
|
||||
@Prop showGivenToUser = false;
|
||||
|
||||
activeDid = "";
|
||||
allContacts: Array<Contact> = [];
|
||||
@@ -111,22 +113,32 @@ export default class GiftedDialog extends Vue {
|
||||
apiServer = "";
|
||||
|
||||
amountInput = "0";
|
||||
callbackOnSuccess?: (amount: number) => void = () => {};
|
||||
customTitle?: string;
|
||||
description = "";
|
||||
givenToUser = false;
|
||||
giver?: GiverInputInfo; // undefined means no identified giver agent
|
||||
giver?: GiverReceiverInputInfo; // undefined means no identified giver agent
|
||||
isTrade = false;
|
||||
offerId = "";
|
||||
receiver?: GiverReceiverInputInfo;
|
||||
unitCode = "HUR";
|
||||
visible = false;
|
||||
|
||||
libsUtil = libsUtil;
|
||||
|
||||
async open(giver?: GiverInputInfo, offerId?: string) {
|
||||
async open(
|
||||
giver?: GiverReceiverInputInfo,
|
||||
receiver?: GiverReceiverInputInfo,
|
||||
offerId?: string,
|
||||
customTitle?: string,
|
||||
callbackOnSuccess?: (amount: number) => void,
|
||||
) {
|
||||
this.customTitle = customTitle;
|
||||
this.description = "";
|
||||
this.giver = giver || {};
|
||||
this.giver = giver;
|
||||
this.receiver = receiver;
|
||||
// if we show "given to user" selection, default checkbox to true
|
||||
this.givenToUser = this.showGivenToUser;
|
||||
this.amountInput = "0";
|
||||
this.callbackOnSuccess = callbackOnSuccess;
|
||||
this.offerId = offerId || "";
|
||||
|
||||
try {
|
||||
@@ -141,7 +153,7 @@ export default class GiftedDialog extends Vue {
|
||||
const allAccounts = await accountsDB.accounts.toArray();
|
||||
this.allMyDids = allAccounts.map((acc) => acc.did);
|
||||
|
||||
if (!this.giver.name) {
|
||||
if (this.giver && !this.giver.name) {
|
||||
this.giver.name = didInfo(
|
||||
this.giver.did,
|
||||
this.activeDid,
|
||||
@@ -196,7 +208,6 @@ export default class GiftedDialog extends Vue {
|
||||
eraseValues() {
|
||||
this.description = "";
|
||||
this.giver = undefined;
|
||||
this.givenToUser = this.showGivenToUser;
|
||||
this.amountInput = "0";
|
||||
this.unitCode = "HUR";
|
||||
}
|
||||
@@ -254,6 +265,7 @@ export default class GiftedDialog extends Vue {
|
||||
// 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,
|
||||
@@ -265,14 +277,16 @@ export default class GiftedDialog extends Vue {
|
||||
/**
|
||||
*
|
||||
* @param giverDid may be null
|
||||
* @param recipientDid may be null
|
||||
* @param description may be an empty string
|
||||
* @param amountInput may be 0
|
||||
* @param amount may be 0
|
||||
* @param unitCode may be omitted, defaults to "HUR"
|
||||
*/
|
||||
public async recordGive(
|
||||
async recordGive(
|
||||
giverDid: string | null,
|
||||
recipientDid: string | null,
|
||||
description: string,
|
||||
amountInput: number,
|
||||
amount: number,
|
||||
unitCode: string = "HUR",
|
||||
) {
|
||||
try {
|
||||
@@ -282,9 +296,9 @@ export default class GiftedDialog extends Vue {
|
||||
this.apiServer,
|
||||
identity,
|
||||
giverDid,
|
||||
this.givenToUser ? this.activeDid : undefined,
|
||||
this.receiver?.did as string,
|
||||
description,
|
||||
amountInput,
|
||||
amount,
|
||||
unitCode,
|
||||
this.projectId,
|
||||
this.offerId,
|
||||
@@ -316,6 +330,9 @@ export default class GiftedDialog extends Vue {
|
||||
},
|
||||
7000,
|
||||
);
|
||||
if (this.callbackOnSuccess) {
|
||||
this.callbackOnSuccess(amount);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<input
|
||||
type="text"
|
||||
class="w-full border border-slate-400 px-2 py-2 rounded-r"
|
||||
:placeholder="'Date, eg. ' + new Date().toISOString().slice(0, 10)"
|
||||
:placeholder="datePlaceholder()"
|
||||
v-model="expirationDateInput"
|
||||
/>
|
||||
</div>
|
||||
@@ -69,6 +69,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { DateTime } from "luxon";
|
||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||
|
||||
import { NotificationIface } from "@/constants/app";
|
||||
@@ -143,6 +144,12 @@ export default class OfferDialog extends Vue {
|
||||
)}`;
|
||||
}
|
||||
|
||||
datePlaceholder() {
|
||||
return (
|
||||
"Date, eg. " + DateTime.now().plus({ month: 1 }).toISO().slice(0, 10)
|
||||
);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.close();
|
||||
this.eraseValues();
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
:to="{ name: 'projects' }"
|
||||
class="block text-center py-3 px-1"
|
||||
>
|
||||
<fa icon="folder-open" class="fa-fw"></fa>
|
||||
<fa icon="hand" class="fa-fw"></fa>
|
||||
</router-link>
|
||||
</li>
|
||||
<!-- Contacts -->
|
||||
|
||||
@@ -27,14 +27,14 @@ export interface AgreeVerifiableCredential {
|
||||
object: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface GiverInputInfo {
|
||||
export interface GiverReceiverInputInfo {
|
||||
did?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface GiverOutputInfo {
|
||||
action: string;
|
||||
giver?: GiverInputInfo;
|
||||
giver?: GiverReceiverInputInfo;
|
||||
description?: string;
|
||||
amount?: number;
|
||||
unitCode?: string;
|
||||
@@ -481,7 +481,7 @@ export async function getPlanFromCache(
|
||||
cred = resp.data.data[0];
|
||||
planCache.set(handleId, cred);
|
||||
} else {
|
||||
console.log(
|
||||
console.error(
|
||||
"Failed to load plan with handle",
|
||||
handleId,
|
||||
" Got data:",
|
||||
@@ -489,7 +489,7 @@ export async function getPlanFromCache(
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
console.error(
|
||||
"Failed to load plan with handle",
|
||||
handleId,
|
||||
" Got error:",
|
||||
|
||||
@@ -104,7 +104,8 @@
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<div class="flex justify-center">
|
||||
... and those without your image see this (if you let them see your activity):
|
||||
... and those without your image see this (if you let them see your
|
||||
activity):
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<EntityIcon
|
||||
@@ -1400,7 +1401,7 @@ export default class AccountViewView extends Vue {
|
||||
console.error("Error deleting image:", error);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((error as any).response.status === 404) {
|
||||
console.log("The image was already deleted:", error);
|
||||
console.error("The image was already deleted:", error);
|
||||
|
||||
await db.open();
|
||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
|
||||
@@ -415,7 +415,7 @@ import * as serverUtil from "@/libs/endorserServer";
|
||||
import * as libsUtil from "@/libs/util";
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import { Account } from "@/db/tables/accounts";
|
||||
import { GiverInputInfo } from "@/libs/endorserServer";
|
||||
import { GiverReceiverInputInfo } from "@/libs/endorserServer";
|
||||
|
||||
@Component({
|
||||
components: { GiftedDialog, QuickNav },
|
||||
@@ -775,11 +775,12 @@ export default class ClaimView extends Vue {
|
||||
}
|
||||
|
||||
openFulfillGiftDialog() {
|
||||
const giver: GiverInputInfo = {
|
||||
const giver: GiverReceiverInputInfo = {
|
||||
did: libsUtil.offerGiverDid(this.veriClaim),
|
||||
};
|
||||
(this.$refs.customGiveDialog as GiftedDialog).open(
|
||||
giver,
|
||||
undefined,
|
||||
this.veriClaim.handleId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ import { Account, AccountsSchema } from "@/db/tables/accounts";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import { GiverInputInfo } from "@/libs/endorserServer";
|
||||
import { GiverReceiverInputInfo } from "@/libs/endorserServer";
|
||||
|
||||
@Component({
|
||||
components: { GiftedDialog, QuickNav, EntityIcon },
|
||||
@@ -163,8 +163,11 @@ export default class ContactGiftingView extends Vue {
|
||||
return headers;
|
||||
}
|
||||
|
||||
openDialog(giver: GiverInputInfo) {
|
||||
(this.$refs.customDialog as GiftedDialog).open(giver);
|
||||
openDialog(giver: GiverReceiverInputInfo) {
|
||||
const recipient = this.projectId
|
||||
? undefined
|
||||
: { did: this.activeDid, name: "you" };
|
||||
(this.$refs.customDialog as GiftedDialog).open(giver, recipient);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -188,7 +188,6 @@ export default class ContactQRScanShow extends Vue {
|
||||
if (url) {
|
||||
try {
|
||||
const fullData = getContactPayloadFromJwtUrl(url);
|
||||
console.log("fullData", fullData);
|
||||
localStorage.setItem("contactEndorserUrl", url);
|
||||
this.$router.push({ name: "contacts" });
|
||||
} catch (e) {
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -18,8 +18,12 @@
|
||||
<h1 class="text-4xl text-center font-light px-4 mb-4">What Was Given</h1>
|
||||
|
||||
<h1 class="text-xl font-bold text-center mb-4">
|
||||
{{ message }} {{ giverName || "somebody not named" }}
|
||||
{{ customTitle || message + " " + giverName || "somebody not named" }}
|
||||
</h1>
|
||||
<div>
|
||||
<span>From {{ giverName || "somebody not named" }}</span>
|
||||
<span> to {{ recipientName || "somebody not named" }}</span>
|
||||
</div>
|
||||
<textarea
|
||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||
placeholder="What was received"
|
||||
@@ -80,7 +84,7 @@
|
||||
<label class="text-sm">This is given to a project</label>
|
||||
</div>
|
||||
|
||||
<div v-if="!projectId" class="mt-4">
|
||||
<div v-if="showGivenToUser" class="mt-4">
|
||||
<input type="checkbox" class="h-6 w-6 mr-2" v-model="givenToUser" />
|
||||
<label class="text-sm">Given to you</label>
|
||||
</div>
|
||||
@@ -126,12 +130,10 @@ import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||
import { createAndSubmitGive } from "@/libs/endorserServer";
|
||||
import * as libsUtil from "@/libs/util";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||
import GiftedPhotoDialog from "@/components/GiftedPhotoDialog.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
GiftedDialog,
|
||||
GiftedPhotoDialog,
|
||||
QuickNav,
|
||||
TopMessage,
|
||||
@@ -144,6 +146,7 @@ export default class GiftedDetails extends Vue {
|
||||
apiServer = "";
|
||||
|
||||
amountInput = "0";
|
||||
customTitle = "";
|
||||
description = "";
|
||||
givenToUser = false;
|
||||
giverDid: string | undefined;
|
||||
@@ -153,30 +156,46 @@ export default class GiftedDetails extends Vue {
|
||||
message = "";
|
||||
offerId = "";
|
||||
projectId = "";
|
||||
recipientDid = "";
|
||||
recipientName = "";
|
||||
showGivenToUser = false;
|
||||
unitCode = "HUR";
|
||||
|
||||
libsUtil = libsUtil;
|
||||
|
||||
async mounted() {
|
||||
this.amountInput = this.$route.query.amountInput as string;
|
||||
this.customTitle = this.$route.query.customTitle as string;
|
||||
this.description = this.$route.query.description as string;
|
||||
this.giverDid = this.$route.query.giverDid as string;
|
||||
this.giverName = this.$route.query.giverName as string;
|
||||
if (this.giverDid && !this.giverName) {
|
||||
this.giverName =
|
||||
this.giverDid === this.activeDid ? "you" : "someone not named";
|
||||
}
|
||||
this.message = this.$route.query.message as string;
|
||||
this.offerId = this.$route.query.offerId as string;
|
||||
this.projectId = this.$route.query.projectId as string;
|
||||
this.recipientDid = this.$route.query.recipientDid as string;
|
||||
this.recipientName = this.$route.query.recipientName as string;
|
||||
if (this.recipientDid && !this.recipientName) {
|
||||
this.recipientName =
|
||||
this.recipientDid === this.activeDid ? "you" : "someone not named";
|
||||
}
|
||||
this.unitCode = this.$route.query.unitCode as string;
|
||||
|
||||
this.imageUrl = localStorage.getItem("imageUrl") || "";
|
||||
|
||||
this.givenToUser = !this.projectId;
|
||||
|
||||
try {
|
||||
await db.open();
|
||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
||||
this.apiServer = settings?.apiServer || "";
|
||||
this.activeDid = settings?.activeDid || "";
|
||||
|
||||
this.givenToUser = this.recipientDid === this.activeDid;
|
||||
this.showGivenToUser =
|
||||
!this.projectId && this.recipientDid === this.activeDid;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
console.error("Error retrieving settings from database:", err);
|
||||
@@ -358,12 +377,18 @@ export default class GiftedDetails extends Vue {
|
||||
public async recordGive() {
|
||||
try {
|
||||
const identity = await libsUtil.getIdentity(this.activeDid);
|
||||
const recipientDid =
|
||||
this.recipientDid === this.activeDid
|
||||
? this.givenToUser
|
||||
? this.activeDid
|
||||
: undefined
|
||||
: this.recipientDid;
|
||||
const result = await createAndSubmitGive(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
identity,
|
||||
this.giverDid,
|
||||
this.givenToUser ? this.activeDid : undefined,
|
||||
recipientDid,
|
||||
this.description,
|
||||
parseFloat(this.amountInput),
|
||||
this.unitCode,
|
||||
|
||||
@@ -154,13 +154,13 @@
|
||||
<router-link
|
||||
v-if="allContacts.length >= 7"
|
||||
:to="{ name: 'contact-gives' }"
|
||||
class="block text-center text-md font-bold 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-2 py-3 rounded-md"
|
||||
class="block text-center text-md font-bold 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-3 rounded-md"
|
||||
>
|
||||
Choose From All Contacts
|
||||
</router-link>
|
||||
<button
|
||||
@click="openGiftedPrompts()"
|
||||
class="block 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-4 py-2 rounded-md"
|
||||
class="block text-center text-md 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-4 py-2 rounded-md"
|
||||
>
|
||||
Ideas...
|
||||
</button>
|
||||
@@ -168,11 +168,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<GiftedDialog
|
||||
ref="customDialog"
|
||||
message="Received from"
|
||||
showGivenToUser="true"
|
||||
/>
|
||||
<GiftedDialog ref="customDialog" message="Received from" />
|
||||
<GiftedPrompts ref="giftedPrompts" />
|
||||
<FeedFilters ref="feedFilters" />
|
||||
|
||||
@@ -181,7 +177,7 @@
|
||||
<div class="flex items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Latest Activity</h2>
|
||||
<button @click="openFeedFilters()" class="block text-center ml-auto">
|
||||
<span class="text-sm uppercase text-white">
|
||||
<span class="text-sm text-white">
|
||||
<span
|
||||
v-if="resultsAreFiltered()"
|
||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md"
|
||||
@@ -328,7 +324,7 @@ import {
|
||||
containsNonHiddenDid,
|
||||
didInfoForContact,
|
||||
getPlanFromCache,
|
||||
GiverInputInfo,
|
||||
GiverReceiverInputInfo,
|
||||
GiveSummaryRecord,
|
||||
} from "@/libs/endorserServer";
|
||||
import { generateSaveAndActivateIdentity } from "@/libs/util";
|
||||
@@ -722,8 +718,11 @@ export default class HomeView extends Vue {
|
||||
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
||||
}
|
||||
|
||||
openDialog(giver?: GiverInputInfo) {
|
||||
(this.$refs.customDialog as GiftedDialog).open(giver);
|
||||
openDialog(giver?: GiverReceiverInputInfo) {
|
||||
(this.$refs.customDialog as GiftedDialog).open(giver, {
|
||||
did: this.activeDid,
|
||||
name: "you",
|
||||
});
|
||||
}
|
||||
|
||||
openGiftedPrompts() {
|
||||
|
||||
@@ -363,7 +363,7 @@ import * as libsUtil from "@/libs/util";
|
||||
import {
|
||||
BLANK_GENERIC_SERVER_RECORD,
|
||||
GenericCredWrapper,
|
||||
GiverInputInfo,
|
||||
GiverReceiverInputInfo,
|
||||
GiveSummaryRecord,
|
||||
OfferSummaryRecord,
|
||||
PlanSummaryRecord,
|
||||
@@ -697,7 +697,7 @@ export default class ProjectViewView extends Vue {
|
||||
);
|
||||
}
|
||||
|
||||
openGiftDialog(contact?: GiverInputInfo) {
|
||||
openGiftDialog(contact?: GiverReceiverInputInfo) {
|
||||
(this.$refs.customGiveDialog as GiftedDialog).open(contact);
|
||||
}
|
||||
|
||||
@@ -736,10 +736,14 @@ export default class ProjectViewView extends Vue {
|
||||
claim: offer.fullClaim,
|
||||
issuer: offer.offeredByDid,
|
||||
};
|
||||
const giver: GiverInputInfo = {
|
||||
const giver: GiverReceiverInputInfo = {
|
||||
did: libsUtil.offerGiverDid(offerRecord),
|
||||
};
|
||||
(this.$refs.customGiveDialog as GiftedDialog).open(giver, offer.handleId);
|
||||
(this.$refs.customGiveDialog as GiftedDialog).open(
|
||||
giver,
|
||||
undefined,
|
||||
offer.handleId,
|
||||
);
|
||||
}
|
||||
|
||||
// return an HTTPS URL if it's not a global URL
|
||||
|
||||
@@ -298,7 +298,7 @@ export default class ProjectsView extends Vue {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
const resp = await this.axios.get(url, { headers });
|
||||
if (resp.status === 200 || !resp.data.data) {
|
||||
if (resp.status === 200 && resp.data.data) {
|
||||
const plans: PlanData[] = resp.data.data;
|
||||
for (const plan of plans) {
|
||||
const { name, description, handleId, issuerDid, rowid } = plan;
|
||||
@@ -423,7 +423,7 @@ export default class ProjectsView extends Vue {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
const resp = await this.axios.get(url, { headers });
|
||||
if (resp.status === 200 || !resp.data.data) {
|
||||
if (resp.status === 200 && resp.data.data) {
|
||||
this.offers = this.offers.concat(resp.data.data);
|
||||
} else {
|
||||
console.error(
|
||||
|
||||
@@ -97,6 +97,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
todayOrPreviousStartDate = "";
|
||||
|
||||
async mounted() {
|
||||
// use the time zone for Bountiful
|
||||
let currentOrPreviousSat = DateTime.now().setZone("America/Denver");
|
||||
if (currentOrPreviousSat.weekday < 6) {
|
||||
// it's not Saturday or Sunday,
|
||||
|
||||
@@ -223,7 +223,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
console.log("Bad response", response);
|
||||
console.error("Bad response", response);
|
||||
throw new Error("Bad response when retrieving claims.");
|
||||
}
|
||||
await response.json().then((data) => {
|
||||
|
||||
Reference in New Issue
Block a user