Browse Source

Merge branch 'master' into no-accounts-in-memory

no-accounts-in-memory
anomalist 1 year ago
parent
commit
f269f5fa77
  1. 9
      project.task.yaml
  2. 4
      src/components/AlertMessage.vue
  3. 54
      src/components/GiftedDialog.vue
  4. 2
      src/main.ts
  5. 8
      src/router/index.ts
  6. 16
      src/views/AccountViewView.vue
  7. 2
      src/views/ConfirmContactView.vue
  8. 265
      src/views/ContactGiftingView.vue
  9. 10
      src/views/ContactScanView.vue
  10. 62
      src/views/HomeView.vue
  11. 3
      src/views/NewEditAccountView.vue
  12. 7
      src/views/NewEditCommitmentView.vue
  13. 3
      src/views/ProjectsView.vue

9
project.task.yaml

@ -2,7 +2,7 @@
tasks: tasks:
- 01 add a location for a project via map pin - 01 add a location for a project via map pin
- 04 search by a bounding box for local projects (see API by clicking on "Nearby") - 04 search by a bounding box for local projects (see API by clicking on "Nearby")
- 01 Replace Gifted/Give in ContactsView with GiftedDialog - 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:jose
- 02 Fix images on projectview - allow choice of image from a pallete of images or a url image. - 02 Fix images on projectview - allow choice of image from a pallete of images or a url image.
- 08 Scan QR code to import into contacts. - 08 Scan QR code to import into contacts.
@ -63,9 +63,6 @@ tasks:
- Test PWA features on Android and iOS. - Test PWA features on Android and iOS.
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
- 40 notifications v+ :
- pull, w/ scheduled runs
- linking between projects or plans : - linking between projects or plans :
- show total time given to & from a project - show total time given to & from a project
- terminology: - terminology:
@ -94,6 +91,10 @@ tasks:
- Write to or read from a different ledger (eg. private ACDC, attest.sh) - Write to or read from a different ledger (eg. private ACDC, attest.sh)
- Do we want split first name & last name?
- 40 notifications v+ :
- pull, w/ scheduled runs
log: log:
- videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29 - videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29

4
src/components/AlertMessage.vue

@ -1,7 +1,7 @@
<template> <template>
<div v-bind:class="computedAlertClassNames()"> <div v-bind:class="computedAlertClassNames()">
<button <button
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2" class="close-button bg-amber-400 w-8 leading-loose rounded-full absolute top-2 right-2"
@click="onClickClose()" @click="onClickClose()"
> >
<fa icon="xmark"></fa> <fa icon="xmark"></fa>
@ -28,7 +28,7 @@ export default class AlertMessage extends Vue {
return { return {
hidden: !this.isAlertVisible, hidden: !this.isAlertVisible,
"dismissable-alert": true, "dismissable-alert": true,
"bg-slate-100": true, "bg-amber-200": true,
"p-5": true, "p-5": true,
rounded: true, rounded: true,
"drop-shadow-lg": true, "drop-shadow-lg": true,

54
src/components/GiftedDialog.vue

@ -1,43 +1,53 @@
<template> <template>
<div v-if="visible" class="dialog-overlay"> <div v-if="visible" class="dialog-overlay">
<div class="dialog"> <div class="dialog">
<h1 class="text-lg text-center"> <h1 class="text-xl font-bold text-center mb-4">
{{ message }} {{ giver?.name || "somebody not specified" }} {{ message }} {{ giver?.name || "somebody not specified" }}
</h1> </h1>
<input <input
type="text" type="text"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2" class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
placeholder="What was received" placeholder="What was received"
v-model="description" v-model="description"
/> />
<div class="flex flex-row"> <div class="flex flex-row mb-6">
<span class="py-4">Hours</span> <span
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 w-1/3 text-center px-2 py-2"
>Hours</span
>
<div
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
@click="decrement()"
>
<fa icon="chevron-left" />
</div>
<input <input
type="text" type="text"
class="block w-8 rounded border border-slate-400 ml-4 text-center" class="w-full border border-r-0 border-slate-400 px-2 py-2 text-center"
v-model="hours" v-model="hours"
/> />
<div class="flex flex-col px-1"> <div
<div> class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
<fa icon="square-caret-up" size="2xl" @click="increment()" /> @click="increment()"
</div> >
<div> <fa icon="chevron-right" />
<fa icon="square-caret-down" size="2xl" @click="decrement()" />
</div> </div>
</div> </div>
</div> <p class="text-center mb-2 italic">Sign & Send to publish to the world</p>
<p class="text-right">Sign & Send to publish to the world</p> <button
<div class="text-right"> class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
<button class="rounded border border-slate-400" @click="confirm"> @click="confirm"
<span class="m-2">Sign & Send</span> >
Sign &amp; Send
</button> </button>
&nbsp; <button
<button class="rounded border border-slate-400" @click="cancel"> class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
<span class="m-2">Cancel</span> @click="cancel"
>
Cancel
</button> </button>
</div> </div>
</div> </div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -106,12 +116,14 @@ export default class GiftedDialog extends Vue {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 1.5rem;
} }
.dialog { .dialog {
background-color: white; background-color: white;
padding: 1rem; padding: 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
width: 50%; width: 100%;
max-width: 500px;
} }
</style> </style>

2
src/main.ts

@ -13,6 +13,7 @@ import {
faBurst, faBurst,
faCalendar, faCalendar,
faChevronLeft, faChevronLeft,
faChevronRight,
faCircle, faCircle,
faCircleCheck, faCircleCheck,
faCircleQuestion, faCircleQuestion,
@ -53,6 +54,7 @@ library.add(
faBurst, faBurst,
faCalendar, faCalendar,
faChevronLeft, faChevronLeft,
faChevronRight,
faCircle, faCircle,
faCircleCheck, faCircleCheck,
faCircleQuestion, faCircleQuestion,

8
src/router/index.ts

@ -166,6 +166,14 @@ const routes: Array<RouteRecordRaw> = [
/* webpackChunkName: "statistics" */ "../views/StatisticsView.vue" /* webpackChunkName: "statistics" */ "../views/StatisticsView.vue"
), ),
}, },
{
path: "/contact-gives",
name: "contact-gives",
component: () =>
import(
/* webpackChunkName: "statistics" */ "../views/ContactGiftingView.vue"
),
},
]; ];
/** @type {*} */ /** @type {*} */

16
src/views/AccountViewView.vue

@ -143,7 +143,6 @@
<!-- QR code popup --> <!-- QR code popup -->
<dialog id="dlgQR" class="backdrop:bg-black/75 rounded-md"> <dialog id="dlgQR" class="backdrop:bg-black/75 rounded-md">
<form method="dialog">
<div class="text-slate-500 text-center"> <div class="text-slate-500 text-center">
<b>ID:</b> <code>did:peer:kl45kj41lk451kl3</code> <b>ID:</b> <code>did:peer:kl45kj41lk451kl3</code>
</div> </div>
@ -161,7 +160,6 @@
> >
Close Close
</button> </button>
</form>
</dialog> </dialog>
<h3 <h3
@ -260,20 +258,6 @@
</button> </button>
</div> </div>
<div v-if="numAccounts > 0" class="flex py-2">
Switch Identifier
<span>
<button class="text-blue-500 px-2" @click="switchAccount(0)">
None
</button>
</span>
<span v-for="accountNum in numAccounts" :key="accountNum">
<button class="text-blue-500 px-2" @click="switchAccount(accountNum)">
#{{ accountNum }}
</button>
</span>
</div>
<div> <div>
<button class="text-blue-500"> <button class="text-blue-500">
<router-link <router-link

2
src/views/ConfirmContactView.vue

@ -15,7 +15,6 @@
</h1> </h1>
</div> </div>
<form>
<p class="text-center text-xl mb-4 font-light"> <p class="text-center text-xl mb-4 font-light">
Would you like to add <i>Firstname</i> to your network? Would you like to add <i>Firstname</i> to your network?
</p> </p>
@ -43,7 +42,6 @@
Cancel Cancel
</button> </button>
</div> </div>
</form>
</section> </section>
</template> </template>

265
src/views/ContactGiftingView.vue

@ -0,0 +1,265 @@
<template>
<QuickNav selected="Home"></QuickNav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Back -->
<router-link
:to="{ name: 'home' }"
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
</h1>
</div>
<!-- Quick Search -->
<!-- Initial Loading Animation -->
<!-- Results List -->
<ul class="border-t border-slate-300">
<li class="border-b border-slate-300 py-3">
<h2 class="text-base flex gap-4 items-center">
<span class="grow italic"
><fa icon="question-circle" class="fa-fw fa-xl text-slate-400"></fa>
Anonymous
</span>
<span class="text-right">
<button
type="button"
@click="openDialog()"
class="block w-full text-center text-sm uppercase bg-blue-600 text-white px-3 py-1.5 rounded-md"
>
<fa icon="gift" class="fa-fw"></fa>
</button>
</span>
</h2>
</li>
<li
v-for="contact in allContacts"
:key="contact.did"
class="border-b border-slate-300 py-3"
>
<h2 class="text-base flex gap-4 items-center">
<span class="grow font-semibold"
><fa icon="user" class="fa-fw fa-xl text-slate-400"></fa>
{{ contact.name || "(no name)" }}
</span>
<span class="text-right">
<button
type="button"
@click="openDialog(contact)"
class="block w-full text-center text-sm uppercase bg-blue-600 text-white px-3 py-1.5 rounded-md"
>
<fa icon="gift" class="fa-fw"></fa>
</button>
</span>
</h2>
</li>
</ul>
<GiftedDialog
ref="customDialog"
@dialog-result="handleDialogResult"
message="Received from"
>
</GiftedDialog>
<AlertMessage
:alertTitle="alertTitle"
:alertMessage="alertMessage"
></AlertMessage>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue";
import { db, accountsDB } from "@/db";
import { AccountsSchema } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import { createAndSubmitGive } from "@/libs/endorserServer";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav";
@Component({
components: { GiftedDialog, AlertMessage, QuickNav },
})
export default class HomeView extends Vue {
activeDid = "";
allAccounts: Array<Account> = [];
allContacts: Array<Contact> = [];
apiServer = "";
isHiddenSpinner = true;
alertTitle = "";
alertMessage = "";
accounts: AccountsSchema;
numAccounts = 0;
async beforeCreate() {
accountsDB.open();
this.accounts = accountsDB.accounts;
this.numAccounts = await this.accounts.count();
}
public async getIdentity(activeDid) {
await accountsDB.open();
const account = await accountsDB.accounts
.where("did")
.equals(activeDid)
.first();
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"Attempted to load Give records with no identity available.",
);
}
return identity;
}
public async getHeaders(identity) {
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
return headers;
}
async created() {
try {
await accountsDB.open();
this.allAccounts = await accountsDB.accounts.toArray();
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
this.allContacts = await db.contacts.toArray();
this.feedLastViewedId = settings?.lastViewedClaimId;
this.updateAllFeed();
} catch (err) {
this.alertTitle = "Error";
this.alertMessage =
err.userMessage ||
"There was an error retrieving the latest sweet, sweet action.";
}
}
public async buildHeaders() {
const headers = { "Content-Type": "application/json" };
if (this.activeDid) {
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
const account = allAccounts.find((acc) => acc.did === this.activeDid);
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service.",
);
}
headers["Authorization"] = "Bearer " + (await accessToken(identity));
} else {
// it's OK without auth... we just won't get any identifiers
}
return headers;
}
openDialog(giver) {
this.$refs.customDialog.open(giver);
}
handleDialogResult(result) {
if (result.action === "confirm") {
return new Promise((resolve) => {
this.recordGive(result.contact?.did, result.description, result.hours);
resolve();
});
} else {
// action was "cancel" so do nothing
}
}
/**
*
* @param giverDid may be null
* @param description may be an empty string
* @param hours may be 0
*/
public async recordGive(giverDid, description, hours) {
if (!this.activeDid) {
this.setAlert(
"Error",
"You must select an identity before you can record a give.",
);
return;
}
if (!description && !hours) {
this.setAlert(
"Error",
"You must enter a description or some number of hours.",
);
return;
}
try {
const identity = await this.getIdentity(this.activeDid);
const result = await createAndSubmitGive(
this.axios,
this.apiServer,
identity,
giverDid,
this.activeDid,
description,
hours,
);
if (isGiveCreationError(result)) {
const errorMessage = getGiveCreationErrorMessage(result);
console.log("Error with give result:", result);
this.setAlert(
"Error",
errorMessage || "There was an error recording the give.",
);
} else {
this.setAlert("Success", "That gift was recorded.");
}
} catch (error) {
console.log("Error with give caught:", error);
this.setAlert(
"Error",
getGiveErrorMessage(error) || "There was an error recording the give.",
);
}
}
private setAlert(title, message) {
this.alertTitle = title;
this.alertMessage = message;
}
// Helper functions for readability
isGiveCreationError(result) {
return result.status !== 201 || result.data?.error;
}
getGiveCreationErrorMessage(result) {
return result.data?.error?.message;
}
getGiveErrorMessage(error) {
return error.userMessage || error.response?.data?.error?.message;
}
}
</script>

10
src/views/ContactScanView.vue

@ -14,7 +14,6 @@
</h1> </h1>
</div> </div>
<form>
<h3 class="text-sm uppercase font-semibold mb-2">Scan a QR Code</h3> <h3 class="text-sm uppercase font-semibold mb-2">Scan a QR Code</h3>
<div class="bg-black rounded overflow-hidden relative mb-4"> <div class="bg-black rounded overflow-hidden relative mb-4">
<img src="https://picsum.photos/400/400?random=1" class="w-full" /> <img src="https://picsum.photos/400/400?random=1" class="w-full" />
@ -25,9 +24,7 @@
<!-- Reft --> <!-- Reft -->
<div class="absolute top-1/4 bottom-1/4 left-0 bg-black/50 w-1/4"></div> <div class="absolute top-1/4 bottom-1/4 left-0 bg-black/50 w-1/4"></div>
<!-- Right --> <!-- Right -->
<div <div class="absolute top-1/4 bottom-1/4 right-0 bg-black/50 w-1/4"></div>
class="absolute top-1/4 bottom-1/4 right-0 bg-black/50 w-1/4"
></div>
<!-- Bottom --> <!-- Bottom -->
<div class="absolute bottom-0 left-0 right-0 bg-black/50 h-1/4"></div> <div class="absolute bottom-0 left-0 right-0 bg-black/50 h-1/4"></div>
@ -50,9 +47,7 @@
></div> ></div>
</div> </div>
<h3 class="text-sm uppercase font-semibold mb-2"> <h3 class="text-sm uppercase font-semibold mb-2">or Enter Contact Data</h3>
or Enter Contact Data
</h3>
<input <input
type="text" type="text"
placeholder="Name (optional)" placeholder="Name (optional)"
@ -82,7 +77,6 @@
Cancel Cancel
</button> </button>
</div> </div>
</form>
</section> </section>
</template> </template>

62
src/views/HomeView.vue

@ -7,22 +7,40 @@
</h1> </h1>
<div class="mb-8"> <div class="mb-8">
<h1 class="text-2xl">Quick Action</h1> <h2 class="text-xl font-bold">Quick Action</h2>
<p>Choose a contact to whom to show appreciation:</p> <p class="mb-4">Show appreciation to a contact:</p>
<!-- similar contact selection code is in multiple places -->
<div class="px-4"> <ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
<button <li
v-for="contact in allContacts" v-for="contact in allContacts"
:key="contact.did" :key="contact.did"
@click="openDialog(contact)" @click="openDialog(contact)"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2" >
<div class="mb-1">
<fa icon="user" class="fa-fw fa-xl text-slate-400"></fa>
</div>
<h3
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
> >
{{ contact.name || "(no name)" }} {{ contact.name || "(no name)" }}
</button> </h3>
<span v-if="allContacts.length > 0">&nbsp;or&nbsp;</span> </li>
<button @click="openDialog()" class="text-blue-500"> </ul>
someone not specified
</button> <!-- Ideally, this button should only be visible when the active account has more than 7 or 11 contacts in their list (we want to limit the grid count above to 8 or 12 accounts to keep it compact) -->
<router-link
v-if="allContacts.length > 7"
:to="{ name: 'contact-gives' }"
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
>
Show More Contacts&hellip;
</router-link>
<!-- If there are no contacts, show this instead: -->
<div
class="rounded border border-dashed border-slate-300 bg-slate-100 px-4 py-3 text-center italic text-slate-500"
>
(No contacts to show.)
</div> </div>
</div> </div>
@ -33,29 +51,27 @@
> >
</GiftedDialog> </GiftedDialog>
<div> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<h1 class="text-2xl">Latest Activity</h1> <h2 class="text-xl font-bold mb-4">Latest Activity</h2>
<span :class="{ hidden: isHiddenSpinner }"> <div :class="{ hidden: isHiddenSpinner }">
<fa icon="spinner" class="fa-spin-pulse"></fa> <p class="text-slate-500 text-center italic mt-4 mb-4">
Loading&hellip; <fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
</span> </p>
<ul> </div>
<ul class="border-t border-slate-300">
<li <li
class="border-b border-slate-300 py-2" class="border-b border-slate-300 py-2"
v-for="record in feedData" v-for="record in feedData"
:key="record.jwtId" :key="record.jwtId"
> >
<div <div
class="border-b border-dashed border-slate-400 text-orange-400 py-2 mb-2 font-bold uppercase text-sm" class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm"
v-if="record.jwtId == feedLastViewedId" v-if="record.jwtId == feedLastViewedId"
> >
You've seen all claims below: You've seen all claims below:
</div> </div>
<div class="flex"> <div class="flex">
<fa <fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa>
icon="gift"
class="fa-fw flex-none pt-1 pr-2 text-slate-500"
></fa>
<!-- icon values: "coins" = money; "clock" = time; "gift" = others --> <!-- icon values: "coins" = money; "clock" = time; "gift" = others -->
<span class="">{{ this.giveDescription(record) }}</span> <span class="">{{ this.giveDescription(record) }}</span>
</div> </div>

3
src/views/NewEditAccountView.vue

@ -13,7 +13,7 @@
[New/Edit] Identity [New/Edit] Identity
</h1> </h1>
</div> </div>
<form>
<input <input
type="text" type="text"
placeholder="First Name" placeholder="First Name"
@ -44,7 +44,6 @@
Cancel Cancel
</button> </button>
</div> </div>
</form>
</section> </section>
</template> </template>

7
src/views/NewEditCommitmentView.vue

@ -16,10 +16,8 @@
</div> </div>
<!-- Project Details --> <!-- Project Details -->
<form>
<select <select class="block w-full rounded border border-slate-400 mb-4 px-3 py-2">
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
>
<option disabled>Choose a commitment type</option> <option disabled>Choose a commitment type</option>
<option selected>Time</option> <option selected>Time</option>
<option>Cryptocurrency</option> <option>Cryptocurrency</option>
@ -56,7 +54,6 @@
Maybe Later Maybe Later
</button> </button>
</div> </div>
</form>
</section> </section>
</template> </template>

3
src/views/ProjectsView.vue

@ -7,7 +7,7 @@
</h1> </h1>
<!-- Quick Search --> <!-- Quick Search -->
<form id="QuickSearch" class="mb-4 flex">
<input <input
type="text" type="text"
placeholder="Search…" placeholder="Search…"
@ -18,7 +18,6 @@
> >
<fa icon="magnifying-glass" class="fa-fw"></fa> <fa icon="magnifying-glass" class="fa-fw"></fa>
</button> </button>
</form>
<!-- New Project --> <!-- New Project -->
<button <button

Loading…
Cancel
Save