Browse Source

jump from ideas directly into giving dialog choice

Trent Larson 3 months ago
parent
commit
174c37169f
  1. 21
      src/components/GiftedDialog.vue
  2. 186
      src/components/GiftedPrompts.vue
  3. 11
      src/libs/endorserServer.ts
  4. 5
      src/libs/util.ts
  5. 3
      src/views/ClaimView.vue
  6. 11
      src/views/ContactGiftingView.vue
  7. 5
      src/views/ContactsView.vue
  8. 14
      src/views/HomeView.vue
  9. 5
      src/views/ProjectViewView.vue

21
src/components/GiftedDialog.vue

@ -7,7 +7,7 @@
<input
type="text"
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
placeholder="What was given"
:placeholder="prompt || 'What was given?'"
v-model="description"
/>
<div class="flex flex-row justify-center">
@ -89,11 +89,7 @@
import { Vue, Component, Prop } from "vue-facing-decorator";
import { NotificationIface } from "@/constants/app";
import {
createAndSubmitGive,
didInfo,
GiverReceiverInputInfo,
} from "@/libs/endorserServer";
import { createAndSubmitGive, didInfo } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
@ -114,25 +110,27 @@ export default class GiftedDialog extends Vue {
callbackOnSuccess?: (amount: number) => void = () => {};
customTitle?: string;
description = "";
giver?: GiverReceiverInputInfo; // undefined means no identified giver agent
giver?: libsUtil.GiverReceiverInputInfo; // undefined means no identified giver agent
isTrade = false;
offerId = "";
receiver?: GiverReceiverInputInfo;
prompt = "";
receiver?: libsUtil.GiverReceiverInputInfo;
unitCode = "HUR";
visible = false;
libsUtil = libsUtil;
async open(
giver?: GiverReceiverInputInfo,
receiver?: GiverReceiverInputInfo,
giver?: libsUtil.GiverReceiverInputInfo,
receiver?: libsUtil.GiverReceiverInputInfo,
offerId?: string,
customTitle?: string,
prompt?: string,
callbackOnSuccess?: (amount: number) => void,
) {
this.customTitle = customTitle;
this.description = "";
this.giver = giver;
this.prompt = prompt || "";
this.receiver = receiver;
// if we show "given to user" selection, default checkbox to true
this.amountInput = "0";
@ -207,6 +205,7 @@ export default class GiftedDialog extends Vue {
this.description = "";
this.giver = undefined;
this.amountInput = "0";
this.prompt = "";
this.unitCode = "HUR";
}

186
src/components/GiftedPrompts.vue

@ -19,12 +19,12 @@
</span>
<div class="m-2">
<span v-if="currentIdeaIndex < IDEAS.length">
<span v-if="currentCategory === CATEGORY_IDEAS">
<p class="text-center text-lg font-bold">
{{ IDEAS[currentIdeaIndex] }}
</p>
</span>
<div v-if="currentIdeaIndex == IDEAS.length + 0">
<div v-if="currentCategory === CATEGORY_CONTACTS">
<p class="text-center">
<span
v-if="currentContact == null"
@ -61,7 +61,7 @@
</span>
<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 mt-4"
@click="cancel"
@click="proceed"
>
That's it!
</button>
@ -71,53 +71,90 @@
<script lang="ts">
import { Vue, Component } from "vue-facing-decorator";
import { Router } from "vue-router";
import { AppString, NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { GiverReceiverInputInfo } from "@/libs/util";
@Component
export default class GivenPrompts extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
CATEGORY_CONTACTS = 1;
CATEGORY_IDEAS = 0;
IDEAS = [
"Did anyone fix food for you?",
"Did a family member do something for you?",
"Did anyone give you a compliment?",
"What food did someone fix for you?",
"What did a family member do for you?",
"What compliment did someone give you?",
"Who is someone you can always rely on, and how did they demonstrate that?",
"Did you see anyone give to someone else?",
"Is there someone who you have never met who has helped you somehow?",
"How did an artist or musician or author inspire you?",
"What did you see someone give to someone else?",
"What is a way that someone helped you even though you have never met?",
"How did a musician or author or artist inspire you?",
"What inspiration did you get from someone who handled tragedy well?",
"Did some organization give something worth respect?",
"What is something worth respect that an organization gave you?",
"Who last gave you a good laugh?",
"Do you recall anything that was given to you while you were young?",
"Did someone forgive you or overlook a mistake?",
"Do you know of a way an ancestor contributed to your life?",
"Did anyone give you help at work?",
"What do you recall someone giving you while you were young?",
"Who forgave you or overlooked a mistake?",
"What is a way an ancestor contributed to your life?",
"What kind of help did someone at work give you?",
"How did a teacher or mentor or great example help you?",
];
OTHER_PROMPTS = 1;
CONTACT_PROMPT_INDEX = this.IDEAS.length; // expected after other prompts
callbackOnFullGiftInfo?: (
contactInfo?: GiverReceiverInputInfo,
description?: string,
) => void;
currentCategory = this.CATEGORY_IDEAS; // 0 = IDEAS, 1 = CONTACTS
currentContact: Contact | undefined = undefined;
currentIdeaIndex = 0;
numContacts = 0;
shownContactDbIndices: number[] = [];
shownContactDbIndices: Array<boolean> = [];
visible = false;
AppString = AppString;
async open() {
async open(
callbackOnFullGiftInfo: (
contactInfo: GiverReceiverInputInfo,
description: string,
) => void,
) {
this.visible = true;
this.callbackOnFullGiftInfo = callbackOnFullGiftInfo;
await db.open();
this.numContacts = await db.contacts.count();
this.shownContactDbIndices = new Array<boolean>(this.numContacts); // all undefined to start
}
close() {
// close the dialog but don't change values (just in case some actions are added later)
cancel() {
this.currentCategory = this.CATEGORY_IDEAS;
this.currentContact = undefined;
this.currentIdeaIndex = 0;
this.numContacts = 0;
this.shownContactDbIndices = [];
this.visible = false;
}
proceed() {
// proceed with logic but don't change values (just in case some actions are added later)
this.visible = false;
if (this.currentCategory === this.CATEGORY_IDEAS) {
(this.$router as Router).push({
name: "contact-gift",
query: {
prompt: this.IDEAS[this.currentIdeaIndex],
},
});
} else {
// must be this.CATEGORY_CONTACTS
this.callbackOnFullGiftInfo?.(
this.currentContact as GiverReceiverInputInfo,
);
}
}
/**
@ -125,96 +162,77 @@ export default class GivenPrompts extends Vue {
* If it is a contact prompt, loop through.
*/
async nextIdea() {
// if we're incrementing to the contact prompt
// or if we're at the contact prompt and there was a previous contact...
if (
this.currentIdeaIndex == this.CONTACT_PROMPT_INDEX - 1 ||
(this.currentIdeaIndex == this.CONTACT_PROMPT_INDEX &&
this.shownContactDbIndices.length < this.numContacts)
) {
this.currentIdeaIndex = this.CONTACT_PROMPT_INDEX;
// check if the next one is an idea or a contact
if (this.currentCategory === this.CATEGORY_IDEAS) {
this.currentIdeaIndex++;
if (this.currentIdeaIndex === this.IDEAS.length) {
// must have just finished ideas so move to contacts
this.findNextUnshownContact();
}
} else {
// we're not at the contact prompt (or we ran out), so increment the idea index
this.currentIdeaIndex =
(this.currentIdeaIndex + 1) % (this.IDEAS.length + this.OTHER_PROMPTS);
// ... and clear out any other prompt info
this.currentContact = undefined;
this.shownContactDbIndices = [];
// must be this.CATEGORY_CONTACTS
this.findNextUnshownContact();
// when that's finished, it'll reset to ideas
}
}
prevIdea() {
if (
this.currentIdeaIndex ==
(this.CONTACT_PROMPT_INDEX + 1) %
(this.IDEAS.length + this.OTHER_PROMPTS) ||
(this.currentIdeaIndex == this.CONTACT_PROMPT_INDEX &&
this.shownContactDbIndices.length < this.numContacts)
) {
this.currentIdeaIndex = this.CONTACT_PROMPT_INDEX;
this.findNextUnshownContact();
} else {
// we're not at the contact prompt (or we ran out), so increment the idea index
/**
* Get the previous idea.
* If it is a contact prompt, loop through.
*/
async prevIdea() {
// check if the next one is an idea or a contact
if (this.currentCategory === this.CATEGORY_IDEAS) {
this.currentIdeaIndex--;
if (this.currentIdeaIndex < 0) {
this.currentIdeaIndex = this.IDEAS.length - 1 + this.OTHER_PROMPTS;
// must have just finished ideas so move to contacts
this.findNextUnshownContact();
}
// ... and clear out any other prompt info
this.currentContact = undefined;
this.shownContactDbIndices = [];
} else {
// must be this.CATEGORY_CONTACTS
this.findNextUnshownContact();
// when that's finished, it'll reset to ideas
}
}
nextIdeaPastContacts() {
this.currentIdeaIndex = 0;
this.currentContact = undefined;
this.shownContactDbIndices = [];
this.shownContactDbIndices = new Array<boolean>(this.numContacts);
this.currentCategory = this.CATEGORY_IDEAS;
// look at the previous idea and switch to the other side of the list
this.currentIdeaIndex =
this.currentIdeaIndex >= this.IDEAS.length ? 0 : this.IDEAS.length - 1;
}
async findNextUnshownContact() {
// get a random contact
if (this.shownContactDbIndices.length === this.numContacts) {
// no more contacts to show
this.currentContact = undefined;
} else {
// get a random contact that hasn't been shown yet
if (this.currentCategory === this.CATEGORY_IDEAS) {
// we're not in the contact prompts, so reset index array
this.shownContactDbIndices = new Array<boolean>(this.numContacts);
}
this.currentCategory = this.CATEGORY_CONTACTS;
let someContactDbIndex = Math.floor(Math.random() * this.numContacts);
// and guarantee that one is found by walking past shown contacts
let shownContactIndex =
this.shownContactDbIndices.indexOf(someContactDbIndex);
while (shownContactIndex !== -1) {
// increment both indices until we find a spot where "shown" skips a spot
shownContactIndex = (shownContactIndex + 1) % this.numContacts;
someContactDbIndex = (someContactDbIndex + 1) % this.numContacts;
if (
this.shownContactDbIndices[shownContactIndex] !== someContactDbIndex
let count = 0;
// as long as the index has an entry, loop
while (
this.shownContactDbIndices[someContactDbIndex] != null &&
count++ < this.numContacts
) {
// we found a contact that hasn't been shown yet
break;
}
// continue
// ... and there must be at least one because shownContactDbIndices length < numContacts
someContactDbIndex = (someContactDbIndex + 1) % this.numContacts;
}
this.shownContactDbIndices.push(someContactDbIndex);
this.shownContactDbIndices.sort();
if (count >= this.numContacts) {
// all contacts have been shown
this.nextIdeaPastContacts();
} else {
// get the contact at that offset
await db.open();
this.currentContact = await db.contacts
.offset(someContactDbIndex)
.first();
this.shownContactDbIndices[someContactDbIndex] = true;
}
}
cancel() {
this.currentContact = undefined;
this.currentIdeaIndex = 0;
this.numContacts = 0;
this.shownContactDbIndices = [];
this.close();
}
}
</script>

11
src/libs/endorserServer.ts

@ -8,7 +8,11 @@ import { DEFAULT_IMAGE_API_SERVER } from "@/constants/app";
import { Contact } from "@/db/tables/contacts";
import { accessToken, deriveAddress, nextDerivationPath } from "@/libs/crypto";
import { NonsensitiveDexie } from "@/db/index";
import { getAccount, getPasskeyExpirationSeconds } from "@/libs/util";
import {
getAccount,
getPasskeyExpirationSeconds,
GiverReceiverInputInfo,
} from "@/libs/util";
import { createEndorserJwtForKey, KeyMeta } from "@/libs/crypto/vc";
import { Account } from "@/db/tables/accounts";
@ -32,11 +36,6 @@ export interface AgreeVerifiableCredential {
object: Record<string, any>;
}
export interface GiverReceiverInputInfo {
did?: string;
name?: string;
}
export interface GiverOutputInfo {
action: string;
giver?: GiverReceiverInputInfo;

5
src/libs/util.ts

@ -25,6 +25,11 @@ import { KeyMeta } from "@/libs/crypto/vc";
import { createPeerDid } from "@/libs/crypto/vc/didPeer";
import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer";
export interface GiverReceiverInputInfo {
did?: string;
name?: string;
}
export const PRIVACY_MESSAGE =
"The data you send will be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to them and others you explicitly allow.";
export const SHARED_PHOTO_BASE64_KEY = "shared-photo-base64";

3
src/views/ClaimView.vue

@ -463,7 +463,6 @@ import QuickNav from "@/components/QuickNav.vue";
import { Account } from "@/db/tables/accounts";
import {
GenericCredWrapper,
GiverReceiverInputInfo,
OfferVerifiableCredential,
} from "@/libs/endorserServer";
@ -811,7 +810,7 @@ export default class ClaimView extends Vue {
}
openFulfillGiftDialog() {
const giver: GiverReceiverInputInfo = {
const giver: libsUtil.GiverReceiverInputInfo = {
did: libsUtil.offerGiverDid(
this.veriClaim as GenericCredWrapper<OfferVerifiableCredential>,
),

11
src/views/ContactGiftingView.vue

@ -11,8 +11,7 @@
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
><fa icon="chevron-left" class="fa-fw"></fa
></router-link>
Give to Contacts
Given by...
</h1>
</div>
@ -72,6 +71,7 @@
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import GiftedDialog from "@/components/GiftedDialog.vue";
import QuickNav from "@/components/QuickNav.vue";
@ -80,7 +80,7 @@ import { NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { GiverReceiverInputInfo } from "@/libs/endorserServer";
import { GiverReceiverInputInfo } from "@/libs/util";
@Component({
components: { GiftedDialog, QuickNav, EntityIcon },
@ -91,7 +91,9 @@ export default class ContactGiftingView extends Vue {
activeDid = "";
allContacts: Array<Contact> = [];
apiServer = "";
description = "";
projectId = localStorage.getItem("projectId") || "";
prompt = "What was given";
async created() {
try {
@ -107,6 +109,8 @@ export default class ContactGiftingView extends Vue {
(a.name || "").localeCompare(b.name || ""),
);
this.prompt = (this.$route as Router).query["prompt"] ?? this.prompt;
localStorage.removeItem("projectId");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -135,6 +139,7 @@ export default class ContactGiftingView extends Vue {
recipient,
undefined,
"Given by " + (giver?.name || "someone not named"),
this.prompt,
);
}
}

5
src/views/ContactsView.vue

@ -295,7 +295,6 @@ import { getContactPayloadFromJwtUrl } from "@/libs/crypto";
import {
CONTACT_CSV_HEADER,
CONTACT_URL_PREFIX,
GiverReceiverInputInfo,
GiveSummaryRecord,
getHeaders,
isDid,
@ -1010,7 +1009,8 @@ export default class ContactsView extends Vue {
}
private showGiftedDialog(giverDid: string, recipientDid: string) {
let giver: GiverReceiverInputInfo, receiver: GiverReceiverInputInfo;
let giver: libsUtil.GiverReceiverInputInfo;
let receiver: libsUtil.GiverReceiverInputInfo;
if (giverDid) {
giver = {
did: giverDid,
@ -1048,6 +1048,7 @@ export default class ContactsView extends Vue {
receiver,
undefined as string,
customTitle,
undefined as string,
callback,
);
}

14
src/views/HomeView.vue

@ -136,6 +136,9 @@
Unnamed/Unknown
</h3>
</li>
<li v-if="allContacts.length === 0" class="text-sm">
(Add contacts to see more people worthy of recognition.)
</li>
<li
v-for="contact in allContacts.slice(0, 6)"
:key="contact.did"
@ -337,11 +340,11 @@ import {
fetchEndorserRateLimits,
getHeaders,
getPlanFromCache,
GiverReceiverInputInfo,
GiveSummaryRecord,
} from "@/libs/endorserServer";
import {
generateSaveAndActivateIdentity,
GiverReceiverInputInfo,
registerSaveAndActivatePasskey,
} from "@/libs/util";
@ -740,20 +743,23 @@ export default class HomeView extends Vue {
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
}
openDialog(giver?: GiverReceiverInputInfo) {
openDialog(giver?: GiverReceiverInputInfo, description?: string) {
(this.$refs.customDialog as GiftedDialog).open(
giver,
{
did: this.activeDid,
name: "you",
},
} as GiverReceiverInputInfo,
undefined,
"Given by " + (giver?.name || "someone not named"),
description,
);
}
openGiftedPrompts() {
(this.$refs.giftedPrompts as GiftedPrompts).open();
(this.$refs.giftedPrompts as GiftedPrompts).open((giver, description) =>
this.openDialog(giver as GiverReceiverInputInfo, description),
);
}
openFeedFilters() {

5
src/views/ProjectViewView.vue

@ -440,7 +440,6 @@ import {
BLANK_GENERIC_SERVER_RECORD,
GenericCredWrapper,
getHeaders,
GiverReceiverInputInfo,
GiveSummaryRecord,
GiveVerifiableCredential,
OfferSummaryRecord,
@ -861,7 +860,7 @@ export default class ProjectViewView extends Vue {
);
}
openGiftDialog(contact?: GiverReceiverInputInfo) {
openGiftDialog(contact?: libsUtil.GiverReceiverInputInfo) {
(this.$refs.customGiveDialog as GiftedDialog).open(
contact,
undefined,
@ -905,7 +904,7 @@ export default class ProjectViewView extends Vue {
claim: offer.fullClaim,
issuer: offer.offeredByDid,
};
const giver: GiverReceiverInputInfo = {
const giver: libsUtil.GiverReceiverInputInfo = {
did: libsUtil.offerGiverDid(offerRecord),
};
(this.$refs.customGiveDialog as GiftedDialog).open(

Loading…
Cancel
Save