Browse Source

Merge pull request 'Updates to contacts UI. Sweep for buildIdentity and buildHeaders' (#35) from contacts-identity into master

Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/kick-starter-for-time-pwa/pulls/35
kb/add-usage-guide
anomalist 1 year ago
parent
commit
81dd6eb595
  1. 11
      project.task.yaml
  2. 6
      src/router/index.ts
  3. 14
      src/views/AboutView.vue
  4. 106
      src/views/AccountViewView.vue
  5. 58
      src/views/ContactAmountsView.vue
  6. 31
      src/views/ContactQRScanShowView.vue
  7. 203
      src/views/ContactsView.vue
  8. 115
      src/views/HomeView.vue
  9. 56
      src/views/NewEditProjectView.vue
  10. 87
      src/views/ProjectViewView.vue
  11. 39
      src/views/ProjectsView.vue

11
project.task.yaml

@ -1,13 +1,7 @@
tasks: tasks:
- if there's no identity, handle it on pages which expect an identity (eg. project -- look for JSON.parse identity calls)
- .1 show an appropriate message when there are no contacts
- 01 design ideas for simple gives on the Home page - 01 design ideas for simple gives on the Home page
- .1 remove commitments from ProjectView UI
- 01 add list of 'give' records for a project on ProjectView UI - 01 add list of 'give' records for a project on ProjectView UI
- 02 Discover page - display results (currently in console.log), spin when searching - 02 Discover page - display results (currently in console.log), spin when searching
- 08 search by location, endpoint, etc assignee:trent - 08 search by location, endpoint, etc assignee:trent
- 01 add a location for a project via map pin : - 01 add a location for a project via map pin :
@ -39,7 +33,6 @@ tasks:
- 01 quick action - send action, maybe choose via canvas tool https://github.com/konvajs/vue-konva - 01 quick action - send action, maybe choose via canvas tool https://github.com/konvajs/vue-konva
- .5 customize favicon - .5 customize favicon
- .2 Hide "Advanced" section in Account page by default
- 04 allow user to download claims, mine + ones I can see about me from others - 04 allow user to download claims, mine + ones I can see about me from others
- 24 Move to Vite - 24 Move to Vite
@ -57,10 +50,9 @@ tasks:
- 01 link to world for specific stats - 01 link to world for specific stats
- .5 don't load another instance of a bush if it already exists - .5 don't load another instance of a bush if it already exists
- maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version") - maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version")
- 4-8 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie) - 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
- Do we want split first name & last name? - Do we want split first name & last name?
- remove 'about' page
- Release Minimum Viable Product : - Release Minimum Viable Product :
- 08 thorough testing for errors & edge cases - 08 thorough testing for errors & edge cases
@ -99,7 +91,6 @@ tasks:
- Peer DID - Peer DID
- DIDComm - DIDComm
- 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)

6
src/router/index.ts

@ -25,12 +25,6 @@ const routes: Array<RouteRecordRaw> = [
import(/* webpackChunkName: "home" */ "../views/HomeView.vue"), import(/* webpackChunkName: "home" */ "../views/HomeView.vue"),
beforeEnter: enterOrStart, beforeEnter: enterOrStart,
}, },
{
path: "/about",
name: "about",
component: () =>
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
},
{ {
path: "/account", path: "/account",
name: "account", name: "account",

14
src/views/AboutView.vue

@ -1,14 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
@Component({
components: {},
})
export default class AboutView extends Vue {}
</script>

106
src/views/AccountViewView.vue

@ -274,7 +274,6 @@
<script lang="ts"> <script lang="ts">
import "dexie-export-import"; import "dexie-export-import";
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
@ -315,12 +314,38 @@ export default class AccountViewView extends Vue {
limitsMessage = ""; limitsMessage = "";
loadingLimits = true; // might as well now that we do it on mount, to avoid flashing the registration message loadingLimits = true; // might as well now that we do it on mount, to avoid flashing the registration message
showContactGives = false; showContactGives = false;
private accounts: AccountsSchema;
showDidCopy = false; showDidCopy = false;
showDerCopy = false; showDerCopy = false;
showB64Copy = false; showB64Copy = false;
showPubCopy = false; showPubCopy = false;
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;
}
// call fn, copy text to the clipboard, then redo fn after 2 seconds // call fn, copy text to the clipboard, then redo fn after 2 seconds
doCopyTwoSecRedo(text, fn) { doCopyTwoSecRedo(text, fn) {
fn(); fn();
@ -338,7 +363,12 @@ export default class AccountViewView extends Vue {
return timeStr.substring(0, timeStr.indexOf("T")); return timeStr.substring(0, timeStr.indexOf("T"));
} }
// 'created' hook runs when the Vue instance is first created async beforeCreate() {
accountsDB.open();
this.accounts = accountsDB.accounts;
this.numAccounts = await this.accounts.count();
}
async created() { async created() {
// Uncomment this to register this user on the test server. // Uncomment this to register this user on the test server.
// To manage within the vue devtools browser extension https://devtools.vuejs.org/ // To manage within the vue devtools browser extension https://devtools.vuejs.org/
@ -356,16 +386,8 @@ export default class AccountViewView extends Vue {
this.lastName = settings?.lastName || ""; this.lastName = settings?.lastName || "";
this.showContactGives = !!settings?.showContactGivesInline; this.showContactGives = !!settings?.showContactGivesInline;
await accountsDB.open(); const identity = await this.getIdentity(this.activeDid);
const accounts = await accountsDB.accounts.toArray();
this.numAccounts = accounts.length;
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
this.limitsMessage = "No identity.";
this.loadingLimits = false;
return;
}
this.publicHex = identity.keys[0].publicKeyHex; this.publicHex = identity.keys[0].publicKeyHex;
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64"); this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
this.derivationPath = identity.keys[0].meta.derivationPath; this.derivationPath = identity.keys[0].meta.derivationPath;
@ -375,10 +397,21 @@ export default class AccountViewView extends Vue {
}); });
this.checkLimits(); this.checkLimits();
} catch (err) { } catch (err) {
this.alertMessage = if (
"Clear your cache and start over (after data backup)."; err.message ===
console.error("Telling user to clear cache at page create because:", err); "Attempted to load account records with no identity available."
this.alertTitle = "Error Creating Account"; ) {
this.limitsMessage = "No identity.";
this.loadingLimits = false;
} else {
this.alertMessage =
"Clear your cache and start over (after data backup).";
console.error(
"Telling user to clear cache at page create because:",
err,
);
this.alertTitle = "Error Creating Account";
}
} }
} }
@ -424,36 +457,31 @@ export default class AccountViewView extends Vue {
this.loadingLimits = true; this.loadingLimits = true;
this.limitsMessage = ""; this.limitsMessage = "";
const url = this.apiServer + "/api/report/rateLimits";
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
this.limitsMessage = "No identity.";
this.loadingLimits = false;
return;
}
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
try { try {
const url = this.apiServer + "/api/report/rateLimits";
const identity = await this.getIdentity(this.activeDid);
const headers = await this.getHeaders(identity);
const resp = await this.axios.get(url, { headers }); const resp = await this.axios.get(url, { headers });
// axios throws an exception on a 400 // axios throws an exception on a 400
if (resp.status === 200) { if (resp.status === 200) {
this.limits = resp.data; this.limits = resp.data;
} }
} catch (error: unknown) { } catch (error: unknown) {
const serverError = error as AxiosError; if (
error.message ===
console.error("Bad response retrieving limits: ", serverError); "Attempted to load Give records with no identity available."
// Anybody know how to access items inside "response.data" without this? ) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any this.limitsMessage = "No identity.";
const data: any = serverError.response?.data; this.loadingLimits = false;
this.limitsMessage = data?.error?.message || "Bad server response."; } else {
const serverError = error as AxiosError;
console.error("Bad response retrieving limits: ", serverError);
// Anybody know how to access items inside "response.data" without this?
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = serverError.response?.data;
this.limitsMessage = data?.error?.message || "Bad server response.";
}
} }
this.loadingLimits = false; this.loadingLimits = false;

58
src/views/ContactAmountsView.vue

@ -142,8 +142,40 @@ export default class ContactsView extends Vue {
giveRecords: Array<GiveServerRecord> = []; giveRecords: Array<GiveServerRecord> = [];
alertTitle = ""; alertTitle = "";
alertMessage = ""; 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;
}
// 'created' hook runs when the Vue instance is first created
async created() { async created() {
try { try {
await db.open(); await db.open();
@ -166,21 +198,9 @@ export default class ContactsView extends Vue {
} }
async loadGives(activeDid: string, contact: Contact) { async loadGives(activeDid: string, contact: Contact) {
// only load the private keys temporarily when needed
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === activeDid, accounts);
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.",
);
}
// load all the time I have given to them
try { try {
const identity = await this.getIdentity(this.activeDid);
let result = []; let result = [];
const url = const url =
this.apiServer + this.apiServer +
"/api/v2/report/gives?agentDid=" + "/api/v2/report/gives?agentDid=" +
@ -268,15 +288,7 @@ export default class ContactsView extends Vue {
}; };
// Create a signature using private key of identity // Create a signature using private key of identity
await accountsDB.open(); const identity = await this.getIdentity(this.activeDid);
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts);
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.",
);
}
if (identity.keys[0].privateKeyHex !== null) { if (identity.keys[0].privateKeyHex !== null) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const privateKeyHex: string = identity.keys[0].privateKeyHex!; const privateKeyHex: string = identity.keys[0].privateKeyHex!;

31
src/views/ContactQRScanShowView.vue

@ -50,6 +50,29 @@ export default class ContactQRScanShow extends Vue {
apiServer = ""; apiServer = "";
qrValue = ""; qrValue = "";
public async getIdentity(activeDid) {
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === activeDid, accounts);
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() { async created() {
await db.open(); await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);
@ -62,13 +85,7 @@ export default class ContactQRScanShow extends Vue {
if (!account) { if (!account) {
this.alertMessage = "You have no identity yet."; this.alertMessage = "You have no identity yet.";
} else { } else {
const identity = JSON.parse(account?.identity || "null"); const identity = await this.getIdentity(this.activeDid);
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.",
);
}
const publicKeyHex = identity.keys[0].publicKeyHex; const publicKeyHex = identity.keys[0].publicKeyHex;
const publicEncKey = Buffer.from(publicKeyHex, "hex").toString("base64"); const publicEncKey = Buffer.from(publicKeyHex, "hex").toString("base64");
const contactInfo = { const contactInfo = {

203
src/views/ContactsView.vue

@ -70,7 +70,7 @@
</div> </div>
<!-- Results List --> <!-- Results List -->
<ul class=""> <ul v-if="contacts.length > 0">
<li <li
class="border-b border-slate-300" class="border-b border-slate-300"
v-for="contact in contacts" v-for="contact in contacts"
@ -187,6 +187,7 @@
</div> </div>
</li> </li>
</ul> </ul>
<p v-else>This identity has no contacts.</p>
<AlertMessage <AlertMessage
:alertTitle="alertTitle" :alertTitle="alertTitle"
:alertMessage="alertMessage" :alertMessage="alertMessage"
@ -204,7 +205,6 @@ import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken, SimpleSigner } from "@/libs/crypto"; import { accessToken, SimpleSigner } from "@/libs/crypto";
import { import {
GiveServerRecord,
GiveVerifiableCredential, GiveVerifiableCredential,
RegisterVerifiableCredential, RegisterVerifiableCredential,
SERVICE_ID, SERVICE_ID,
@ -261,10 +261,10 @@ export default class ContactsView extends Vue {
); );
} }
async loadGives() { public async getIdentity(activeDid) {
await accountsDB.open(); await accountsDB.open();
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts); const account = R.find((acc) => acc.did === activeDid, accounts);
const identity = JSON.parse(account?.identity || "null"); const identity = JSON.parse(account?.identity || "null");
if (!identity) { if (!identity) {
@ -272,45 +272,43 @@ export default class ContactsView extends Vue {
"Attempted to load Give records with no identity available.", "Attempted to load Give records with no identity available.",
); );
} }
return identity;
}
// load all the time I have given public async getHeaders(identity) {
try { const token = await accessToken(identity);
const url = const headers = {
this.apiServer + "Content-Type": "application/json",
"/api/v2/report/gives?agentDid=" + Authorization: "Bearer " + token,
encodeURIComponent(identity.did); };
const token = await accessToken(identity); return headers;
const headers = { }
"Content-Type": "application/json",
Authorization: "Bearer " + token, public async getHeadersAndIdentity(activeDid) {
}; const identity = await this.getIdentity(activeDid);
const resp = await this.axios.get(url, { headers }); const headers = await this.getHeaders(identity);
//console.log("All gifts you've given:", resp.data);
return { headers, identity };
}
async loadGives() {
const handleResponse = (resp, descriptions, confirmed, unconfirmed) => {
if (resp.status === 200) { if (resp.status === 200) {
const contactDescriptions: Record<string, string> = {}; const allData = resp.data.data;
const contactConfirmed: Record<string, number> = {};
const contactUnconfirmed: Record<string, number> = {};
const allData: Array<GiveServerRecord> = resp.data.data;
for (const give of allData) { for (const give of allData) {
if (give.unit == "HUR") { if (give.unit === "HUR") {
const recipDid: string = give.recipientDid;
if (give.amountConfirmed) { if (give.amountConfirmed) {
const prevAmount = contactConfirmed[recipDid] || 0; const prevAmount = confirmed[give.agentDid] || 0;
contactConfirmed[recipDid] = prevAmount + give.amount; confirmed[give.agentDid] = prevAmount + give.amount;
} else { } else {
const prevAmount = contactUnconfirmed[recipDid] || 0; const prevAmount = unconfirmed[give.agentDid] || 0;
contactUnconfirmed[recipDid] = prevAmount + give.amount; unconfirmed[give.agentDid] = prevAmount + give.amount;
} }
if (!contactDescriptions[recipDid] && give.description) { if (!descriptions[give.agentDid] && give.description) {
// Since many make the tooltip too big, we'll just use the latest. descriptions[give.agentDid] = give.description;
contactDescriptions[recipDid] = give.description;
} }
} }
} }
//console.log("Done retrieving gives", contactConfirmed);
this.givenByMeDescriptions = contactDescriptions;
this.givenByMeConfirmed = contactConfirmed;
this.givenByMeUnconfirmed = contactUnconfirmed;
} else { } else {
console.error( console.error(
"Got bad response status & data of", "Got bad response status & data of",
@ -319,60 +317,56 @@ export default class ContactsView extends Vue {
); );
this.alertTitle = "Error With Server"; this.alertTitle = "Error With Server";
this.alertMessage = this.alertMessage =
"Got an error retrieving your given time from the server."; "Got an error retrieving your " +
resp.config.url.includes("recipientDid")
? "received"
: "given" + " time from the server.";
} }
} catch (error) { };
this.alertTitle = "Error With Server";
this.alertMessage = error as string;
}
// load all the time I have received
try { try {
const url = const { headers, identity } = await this.getHeadersAndIdentity(
this.activeDid,
);
const givenByUrl =
this.apiServer +
"/api/v2/report/gives?agentDid=" +
encodeURIComponent(identity.did);
const givenToUrl =
this.apiServer + this.apiServer +
"/api/v2/report/gives?recipientDid=" + "/api/v2/report/gives?recipientDid=" +
encodeURIComponent(identity.did); encodeURIComponent(identity.did);
const token = await accessToken(identity);
const headers = { const [givenByMeResp, givenToMeResp] = await Promise.all([
"Content-Type": "application/json", this.axios.get(givenByUrl, { headers }),
Authorization: "Bearer " + token, this.axios.get(givenToUrl, { headers }),
}; ]);
const resp = await this.axios.get(url, { headers });
//console.log("All gifts you've recieved:", resp.data); const givenByMeDescriptions = {};
if (resp.status === 200) { const givenByMeConfirmed = {};
const contactDescriptions: Record<string, string> = {}; const givenByMeUnconfirmed = {};
const contactConfirmed: Record<string, number> = {}; handleResponse(
const contactUnconfirmed: Record<string, number> = {}; givenByMeResp,
const allData: Array<GiveServerRecord> = resp.data.data; givenByMeDescriptions,
for (const give of allData) { givenByMeConfirmed,
if (give.unit == "HUR") { givenByMeUnconfirmed,
if (give.amountConfirmed) { );
const prevAmount = contactConfirmed[give.agentDid] || 0; this.givenByMeDescriptions = givenByMeDescriptions;
contactConfirmed[give.agentDid] = prevAmount + give.amount; this.givenByMeConfirmed = givenByMeConfirmed;
} else { this.givenByMeUnconfirmed = givenByMeUnconfirmed;
const prevAmount = contactUnconfirmed[give.agentDid] || 0;
contactUnconfirmed[give.agentDid] = prevAmount + give.amount; const givenToMeDescriptions = {};
} const givenToMeConfirmed = {};
if (!contactDescriptions[give.agentDid] && give.description) { const givenToMeUnconfirmed = {};
// Since many make the tooltip too big, we'll just use the latest. handleResponse(
contactDescriptions[give.agentDid] = give.description; givenToMeResp,
} givenToMeDescriptions,
} givenToMeConfirmed,
} givenToMeUnconfirmed,
//console.log("Done retrieving receipts", contactConfirmed); );
this.givenToMeDescriptions = contactDescriptions; this.givenToMeDescriptions = givenToMeDescriptions;
this.givenToMeConfirmed = contactConfirmed; this.givenToMeConfirmed = givenToMeConfirmed;
this.givenToMeUnconfirmed = contactUnconfirmed; this.givenToMeUnconfirmed = givenToMeUnconfirmed;
} else {
console.error(
"Got bad response status & data of",
resp.status,
resp.data,
);
this.alertTitle = "Error With Server";
this.alertMessage =
"Got an error retrieving your received time from the server.";
}
} catch (error) { } catch (error) {
this.alertTitle = "Error With Server"; this.alertTitle = "Error With Server";
this.alertMessage = error as string; this.alertMessage = error as string;
@ -430,15 +424,8 @@ export default class ContactsView extends Vue {
"?", "?",
) )
) { ) {
await accountsDB.open(); const identity = await this.getIdentity(this.activeDid);
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error("No identity found.");
}
// Make a claim
const vcClaim: RegisterVerifiableCredential = { const vcClaim: RegisterVerifiableCredential = {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "RegisterAction", "@type": "RegisterAction",
@ -518,19 +505,8 @@ export default class ContactsView extends Vue {
this.apiServer + this.apiServer +
"/api/report/" + "/api/report/" +
(visibility ? "canSeeMe" : "cannotSeeMe"); (visibility ? "canSeeMe" : "cannotSeeMe");
await accountsDB.open(); const identity = await this.getIdentity(this.activeDid);
const accounts = await accountsDB.accounts.toArray(); const headers = await this.getHeaders(identity);
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error("No identity found.");
}
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
const payload = JSON.stringify({ did: contact.did }); const payload = JSON.stringify({ did: contact.did });
try { try {
@ -558,19 +534,6 @@ export default class ContactsView extends Vue {
this.apiServer + this.apiServer +
"/api/report/canDidExplicitlySeeMe?did=" + "/api/report/canDidExplicitlySeeMe?did=" +
encodeURIComponent(contact.did); encodeURIComponent(contact.did);
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error("No identity found.");
}
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
try { try {
const resp = await this.axios.get(url, { headers }); const resp = await this.axios.get(url, { headers });
@ -615,13 +578,7 @@ export default class ContactsView extends Vue {
} }
async onClickAddGive(fromDid: string, toDid: string): Promise<void> { async onClickAddGive(fromDid: string, toDid: string): Promise<void> {
await accountsDB.open(); const identity = await this.getIdentity(this.activeDid);
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error("No identity found.");
}
// if they have unconfirmed amounts, ask to confirm those first // if they have unconfirmed amounts, ask to confirm those first
if (toDid == identity?.did && this.givenToMeUnconfirmed[fromDid] > 0) { if (toDid == identity?.did && this.givenToMeUnconfirmed[fromDid] > 0) {

115
src/views/HomeView.vue

@ -17,7 +17,7 @@
@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" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
> >
{{ contact.name }} {{ contact.name || "(no name)" }}
</button> </button>
<span v-if="allContacts.length > 0">&nbsp;or&nbsp;</span> <span v-if="allContacts.length > 0">&nbsp;or&nbsp;</span>
<button @click="openDialog()" class="text-blue-500"> <button @click="openDialog()" class="text-blue-500">
@ -71,7 +71,6 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import { db, accountsDB } from "@/db"; import { db, accountsDB } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@ -97,6 +96,39 @@ export default class HomeView extends Vue {
isHiddenSpinner = true; isHiddenSpinner = true;
alertTitle = ""; alertTitle = "";
alertMessage = ""; 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() { async created() {
try { try {
@ -254,58 +286,57 @@ export default class HomeView extends Vue {
* @param description may be an empty string * @param description may be an empty string
* @param hours may be 0 * @param hours may be 0
*/ */
recordGive(giverDid, description, hours) { public async recordGive(giverDid, description, hours) {
if (!this.activeDid) { if (!this.activeDid) {
this.alertTitle = "Error"; this.setAlert(
this.alertMessage = "Error",
"You must select an identity before you can record a give."; "You must select an identity before you can record a give.",
);
return; return;
} }
if (!description && !hours) { if (!description && !hours) {
this.alertTitle = "Error"; this.setAlert(
this.alertMessage = "Error",
"You must enter a description or some number of hours."; "You must enter a description or some number of hours.",
);
return; return;
} }
const account = this.allAccounts.find((acc) => acc.did === this.activeDid); try {
const identity = JSON.parse(account?.identity || "null"); const identity = await this.getIdentity(this.activeDid);
const result = await createAndSubmitGive(
this.axios,
this.apiServer,
identity,
giverDid,
this.activeDid,
description,
hours,
);
if (!identity) { if (isGiveCreationError(result)) {
throw new Error( const errorMessage = getGiveCreationErrorMessage(result);
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service.", 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.",
); );
} }
}
createAndSubmitGive( private setAlert(title, message) {
this.axios, this.alertTitle = title;
this.apiServer, this.alertMessage = message;
identity,
giverDid,
this.activeDid,
description,
hours,
)
.then((result) => {
if (isGiveCreationError(result)) {
const errorMessage = getGiveCreationErrorMessage(result);
console.log("Error with give result:", result);
this.alertTitle = "Error";
this.alertMessage =
errorMessage || "There was an error recording the give.";
} else {
this.alertTitle = "Success";
this.alertMessage = "That gift was recorded.";
}
})
.catch((error) => {
console.log("Error with give caught:", error);
this.alertTitle = "Error";
this.alertMessage =
getGiveErrorMessage(error) ||
"There was an error recording the give.";
});
} }
// Helper functions for readability // Helper functions for readability

56
src/views/NewEditProjectView.vue

@ -73,7 +73,6 @@
<script lang="ts"> <script lang="ts">
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { accountsDB, db } from "@/db"; import { accountsDB, db } from "@/db";
@ -100,6 +99,39 @@ export default class NewEditProjectView extends Vue {
projectName = ""; projectName = "";
description = ""; description = "";
errorMessage = ""; errorMessage = "";
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 project 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;
}
projectId = localStorage.getItem("projectId") || ""; projectId = localStorage.getItem("projectId") || "";
isHiddenSave = false; isHiddenSave = false;
@ -112,14 +144,10 @@ export default class NewEditProjectView extends Vue {
this.apiServer = settings?.apiServer || ""; this.apiServer = settings?.apiServer || "";
if (this.projectId) { if (this.projectId) {
await accountsDB.open(); if (this.numAccounts === 0) {
const num_accounts = await accountsDB.accounts.count();
if (num_accounts === 0) {
console.error("Error: no account was found."); console.error("Error: no account was found.");
} else { } else {
const accounts = await accountsDB.accounts.toArray(); const identity = await this.getIdentity(this.activeDid);
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "null");
if (!identity) { if (!identity) {
throw new Error( throw new Error(
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service.", "An ID is chosen but there are no keys for it so it cannot be used to talk with the service.",
@ -249,19 +277,11 @@ export default class NewEditProjectView extends Vue {
public async onSaveProjectClick() { public async onSaveProjectClick() {
this.isHiddenSave = true; this.isHiddenSave = true;
this.isHiddenSpinner = false; this.isHiddenSpinner = false;
await accountsDB.open();
const num_accounts = await accountsDB.accounts.count(); if (this.numAccounts === 0) {
if (num_accounts === 0) {
console.error("Error: there is no account."); console.error("Error: there is no account.");
} else { } else {
const accounts = await accountsDB.accounts.toArray(); const identity = await this.getIdentity(this.activeDid);
const account = R.find((acc) => acc.did === this.activeDid, accounts);
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.",
);
}
this.SaveProject(identity); this.SaveProject(identity);
} }
} }

87
src/views/ProjectViewView.vue

@ -97,45 +97,6 @@
message="Received from" message="Received from"
> >
</GiftedDialog> </GiftedDialog>
<!-- Commit -->
<!--
<router-link
:to="{ name: 'new-edit-commitment' }"
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-8"
>Make Commitment</router-link
>
-->
<!-- Commitments -->
<!--
<div class="bg-slate-100 px-4 py-3 rounded-md">
<h3 class="text-sm uppercase font-semibold mb-3">Commitments</h3>
<ul class="text-sm border-t border-slate-300">
<li class="flex justify-between gap-4 py-1.5 border-b border-slate-300">
<span>[Username]</span>
<span
>5 hours <fa icon="spinner" class="fa-fw text-slate-400"></fa
></span>
</li>
<li class="flex justify-between gap-4 py-1.5 border-b border-slate-300">
<span>[Username]</span>
<span
>US$ 20.00 <fa icon="circle-check" class="fa-fw text-lime-500"></fa
></span>
</li>
<li class="flex justify-between gap-4 py-1.5 border-b border-slate-300">
<span>[Username]</span>
<span
>0.1 BTC <fa icon="spinner" class="fa-fw text-slate-400"></fa
></span>
</li>
</ul>
</div>
-->
<AlertMessage <AlertMessage
:alertTitle="alertTitle" :alertTitle="alertTitle"
:alertMessage="alertMessage" :alertMessage="alertMessage"
@ -175,6 +136,39 @@ export default class ProjectViewView extends Vue {
errorMessage = ""; errorMessage = "";
alertMessage = ""; alertMessage = "";
alertTitle = ""; alertTitle = "";
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 project 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;
}
onEditClick() { onEditClick() {
localStorage.setItem("projectId", this.projectId as string); localStorage.setItem("projectId", this.projectId as string);
@ -240,9 +234,7 @@ export default class ProjectViewView extends Vue {
this.apiServer = settings?.apiServer || ""; this.apiServer = settings?.apiServer || "";
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
await accountsDB.open(); if (this.numAccounts === 0) {
const num_accounts = await accountsDB.accounts.count();
if (num_accounts === 0) {
console.error("Problem! Should have a profile!"); console.error("Problem! Should have a profile!");
} else { } else {
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();
@ -293,17 +285,8 @@ export default class ProjectViewView extends Vue {
return; return;
} }
const accounts = await accountsDB.accounts.toArray();
const account = accounts.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.",
);
}
try { try {
const identity = await this.getIdentity(this.activeDid);
const result = await createAndSubmitGive( const result = await createAndSubmitGive(
this.axios, this.axios,
this.apiServer, this.apiServer,

39
src/views/ProjectsView.vue

@ -73,7 +73,6 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { accountsDB, db } from "@/db"; import { accountsDB, db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@ -115,6 +114,14 @@ export default class ProjectsView extends Vue {
isLoading = false; isLoading = false;
alertTitle = ""; alertTitle = "";
alertMessage = ""; alertMessage = "";
accounts: AccountsSchema;
numAccounts = 0;
async beforeCreate() {
accountsDB.open();
this.accounts = accountsDB.accounts;
this.numAccounts = await this.accounts.count();
}
/** /**
* Core project data loader * Core project data loader
@ -183,6 +190,22 @@ export default class ProjectsView extends Vue {
await this.dataLoader(url, token); await this.dataLoader(url, token);
} }
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 project records with no identity available.",
);
}
return identity;
}
/** /**
* 'created' hook runs when the Vue instance is first created * 'created' hook runs when the Vue instance is first created
**/ **/
@ -193,21 +216,13 @@ export default class ProjectsView extends Vue {
const activeDid = settings?.activeDid || ""; const activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || ""; this.apiServer = settings?.apiServer || "";
await accountsDB.open(); if (this.numAccounts === 0) {
const num_accounts = await accountsDB.accounts.count();
if (num_accounts === 0) {
console.error("Problem! You need a profile!"); console.error("Problem! You need a profile!");
this.alertTitle = "Error!"; this.alertTitle = "Error!";
this.alertMessage = "Problem! You need a profile!"; this.alertMessage = "Problem! You need a profile!";
} else { } else {
const accounts = await accountsDB.accounts.toArray(); const identity = await this.getIdentity(activeDid);
const account = R.find((acc) => acc.did === activeDid, accounts); console.log(identity);
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.",
);
}
this.current = identity; this.current = identity;
this.LoadProjects(identity); this.LoadProjects(identity);
} }

Loading…
Cancel
Save