Browse Source

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

kb/add-usage-guide
Trent Larson 7 months ago
parent
commit
d1a285d659
  1. 7
      CHANGELOG.md
  2. 4
      src/components/EntityIcon.vue
  3. 49
      src/components/GiftedDialog.vue
  4. 9
      src/components/OfferDialog.vue
  5. 2
      src/components/QuickNav.vue
  6. 8
      src/libs/endorserServer.ts
  7. 5
      src/views/AccountViewView.vue
  8. 5
      src/views/ClaimView.vue
  9. 9
      src/views/ContactGiftingView.vue
  10. 1
      src/views/ContactQRScanShowView.vue
  11. 256
      src/views/ContactsView.vue
  12. 39
      src/views/GiftedDetails.vue
  13. 21
      src/views/HomeView.vue
  14. 12
      src/views/ProjectViewView.vue
  15. 4
      src/views/ProjectsView.vue
  16. 1
      src/views/QuickActionBvcBeginView.vue
  17. 2
      src/views/QuickActionBvcEndView.vue

7
CHANGELOG.md

@ -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

4
src/components/EntityIcon.vue

@ -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,

49
src/components/GiftedDialog.vue

@ -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) {

9
src/components/OfferDialog.vue

@ -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();

2
src/components/QuickNav.vue

@ -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 -->

8
src/libs/endorserServer.ts

@ -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:",

5
src/views/AccountViewView.vue

@ -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, {

5
src/views/ClaimView.vue

@ -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,
);
}

9
src/views/ContactGiftingView.vue

@ -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>

1
src/views/ContactQRScanShowView.vue

@ -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) {

256
src/views/ContactsView.vue

@ -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 />
-->
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 mb-6"
class="text-center text-md text-white px-1.5 py-2 rounded-md mt-1"
v-bind:class="showGiveAmountsClassNames()"
@click="toggleShowGiveTotals()"
>
{{
showGiveTotals
? "Total"
? "Showing Total"
: showGiveConfirmed
? "Confirmed"
: "Unconfirmed"
}}
</button>
<br />
(Only most recent hours 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>
)
</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,176 +1080,56 @@ 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,
);
}
}
}
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 },
let giver: GiverReceiverInputInfo, receiver: GiverReceiverInputInfo;
if (giverDid) {
giver = {
did: giverDid,
name: this.nameForDid(this.contacts, giverDid),
};
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,
},
if (recipientDid) {
receiver = {
did: recipientDid,
name: this.nameForDid(this.contacts, recipientDid),
};
// 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) {
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[toDid] = (newList[toDid] || 0) + amount;
newList[recipientDid] = (newList[recipientDid] || 0) + amount;
this.givenByMeUnconfirmed = newList;
};
customTitle = "To " + receiver.name;
} 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,
// 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);
}
private async onClickCancelName() {

39
src/views/GiftedDetails.vue

@ -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,

21
src/views/HomeView.vue

@ -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() {

12
src/views/ProjectViewView.vue

@ -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

4
src/views/ProjectsView.vue

@ -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(

1
src/views/QuickActionBvcBeginView.vue

@ -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,

2
src/views/QuickActionBvcEndView.vue

@ -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) => {

Loading…
Cancel
Save