Browse Source

Merge pull request 'add separate screen for amount confirmations' (#15) from separate-dbs into master

Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/kick-starter-for-time-pwa/pulls/15
pull/16/head
anomalist 2 years ago
parent
commit
1d47a90836
  1. 15
      README.md
  2. 13
      project.yaml
  3. 2
      src/constants/app.ts
  4. 3
      src/db/tables/accounts.ts
  5. 3
      src/db/tables/settings.ts
  6. 40
      src/libs/endorserServer.ts
  7. 8
      src/main.ts
  8. 34
      src/router/index.ts
  9. 12
      src/test/index.ts
  10. 105
      src/views/AccountViewView.vue
  11. 3
      src/views/CommitmentsView.vue
  12. 384
      src/views/ContactAmountsView.vue
  13. 286
      src/views/ContactsView.vue
  14. 13
      src/views/ImportAccountView.vue
  15. 6
      src/views/NewEditAccountView.vue
  16. 26
      src/views/NewEditProjectView.vue
  17. 18
      src/views/ProjectViewView.vue
  18. 14
      src/views/ProjectsView.vue

15
README.md

@ -22,7 +22,8 @@ npm run lint
### Clear data & restart
Clear cache for localhost, then go to http://localhost:8080/start (because it'll regenerate if you start on the `/account` page).
Clear cache for localhost, then go to http://localhost:8080/start
(because it'll generate a new one automatically if you start on the `/account` page).
### Test key contents
@ -30,7 +31,8 @@ See [this page](openssl_signing_console.rst)
### Register new user on test server
New users require registration. This can be done with a claim payload like this by an existing user:
New users require registration. This can be done with a claim payload like this
by an existing user:
```
const vcClaim = {
@ -42,18 +44,23 @@ New users require registration. This can be done with a claim payload like this
};
```
On the test server, User #0 has rights to register others, so you can start playing one of two ways:
On the test server, User #0 has rights to register others, so you can start
playing one of two ways:
- Import the keys for the test User `did:ethr:0x000Ee5654b9742f6Fe18ea970e32b97ee2247B51` by importing this seed phrase:
`seminar accuse mystery assist delay law thing deal image undo guard initial shallow wrestle list fragile borrow velvet tomorrow awake explain test offer control`
(Other test users are found [here](https://github.com/trentlarson/endorser-ch/blob/master/test/util.js).)
- Alternatively, register someone else under User #0 on the `/account` page:
- Alternatively, register someone else under User #0 automatically:
* In the `src/views/AccountViewView.vue` file, uncomment the lines referring to "testServerRegisterUser".
* Visit the `/account` page.
### Create multiple identifiers
Go to /import-account and import a new one. Then switch identifiers on the
bottom of the Your Identity page.
### Create keys with alternate tools

13
project.yaml

@ -12,15 +12,16 @@
- replace user-affecting console.logs with error messages (eg. catches)
- contacts v1 :
- test confirmed vs unconfirmed amounts
- remove 'copy' until it works
- switch to prod server
- 01 show gives with confirmations
- .1 change "confirmed" flag to "amountConfirmed" on gives
- .2 warn about amounts when you cannot see them
- .1 remove 'copy' until it works
- .5 switch to prod server
- .5 Add page to show seed.
- 01 Provide a way to import the non-sensitive data.
- 01 Provide way to share your contact info.
- .2 move all "identity" references to temporary account access
- get 'copy' to work on account page
- .5 make deploy for give-only features
- .5 get 'copy' to work on account page
- contacts v+ :
- .5 make advanced "show/hide amounts" button into a nice UI toggle
@ -29,8 +30,10 @@
- refactor UI :
- .5 Alerts show at the top and can be missed, eg. account data download
- 01 Change alerts into a component (to cut down duplicate code)
- 01 Code for "nav" tabs across the bottom is duplicated on each page.
- .2 Add "copied" feedback when they click "copy" on /account
- .5 Fix how icons show on top of bottom bar on ContactAmounts page
- commit screen

2
src/constants/app.ts

@ -2,7 +2,7 @@
* Generic strings that could be used throughout the app.
*/
export enum AppString {
APP_NAME = "Kickstart for time",
APP_NAME = "KickStart with Time",
VERSION = "0.1",
DEFAULT_ENDORSER_API_SERVER = "https://test.endorser.ch:8000",
//DEFAULT_ENDORSER_API_SERVER = "http://localhost:3000",

3
src/db/tables/accounts.ts

@ -2,6 +2,7 @@ export type Account = {
id?: number; // auto-generated by Dexie
dateCreated: string;
derivationPath: string;
did: string;
identity: string;
publicKeyHex: string;
mnemonic: string;
@ -11,5 +12,5 @@ export type Account = {
// see https://github.com/PVermeer/dexie-addon-suite-monorepo/tree/master/packages/dexie-encrypted-addon
export const AccountsSchema = {
accounts:
"++id, dateCreated, derivationPath, $identity, $mnemonic, publicKeyHex",
"++id, dateCreated, derivationPath, did, $identity, $mnemonic, publicKeyHex",
};

3
src/db/tables/settings.ts

@ -1,6 +1,7 @@
// a singleton
export type Settings = {
id: number;
id: number; // there's only one entry: MASTER_SETTINGS_KEY
activeDid?: string;
firstName?: string;
lastName?: string;
showContactGivesInline?: boolean;

40
src/libs/endorserServer.ts

@ -0,0 +1,40 @@
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
export const SERVICE_ID = "endorser.ch";
export interface AgreeVerifiableCredential {
"@context": string;
"@type": string;
// "any" because arbitrary objects can be subject of agreement
// eslint-disable-next-line @typescript-eslint/no-explicit-any
object: Record<any, any>;
}
export interface GiveServerRecord {
agentDid: string;
amount: number;
amountConfirmed: number;
description: string;
fullClaim: GiveVerifiableCredential;
handleId: string;
issuedAt: string;
recipientDid: string;
unit: string;
}
export interface GiveVerifiableCredential {
"@context"?: string; // optional when embedded, eg. in an Agree
"@type": string;
agent: { identifier: string };
description?: string;
identifier?: string;
object: { amountOfThisGood: number; unitCode: string };
recipient: { identifier: string };
}
export interface RegisterVerifiableCredential {
"@context": string;
"@type": string;
agent: { identifier: string };
object: string;
recipient: { identifier: string };
}

8
src/main.ts

@ -12,6 +12,7 @@ import { library } from "@fortawesome/fontawesome-svg-core";
import {
faCalendar,
faChevronLeft,
faCircle,
faCircleCheck,
faCircleQuestion,
faCircleUser,
@ -19,9 +20,12 @@ import {
faEllipsisVertical,
faEye,
faEyeSlash,
faFileLines,
faFolderOpen,
faHand,
faHouseChimney,
faLongArrowAltLeft,
faLongArrowAltRight,
faMagnifyingGlass,
faPen,
faPersonCircleCheck,
@ -40,6 +44,7 @@ import {
library.add(
faCalendar,
faChevronLeft,
faCircle,
faCircleCheck,
faCircleQuestion,
faCircleUser,
@ -47,9 +52,12 @@ library.add(
faEllipsisVertical,
faEye,
faEyeSlash,
faFileLines,
faFolderOpen,
faHand,
faHouseChimney,
faLongArrowAltLeft,
faLongArrowAltRight,
faMagnifyingGlass,
faPen,
faPersonCircleCheck,

34
src/router/index.ts

@ -23,12 +23,6 @@ const routes: Array<RouteRecordRaw> = [
component: () =>
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
},
{
path: "/start",
name: "start",
component: () =>
import(/* webpackChunkName: "start" */ "../views/StartView.vue"),
},
{
path: "/account",
name: "account",
@ -43,6 +37,14 @@ const routes: Array<RouteRecordRaw> = [
/* webpackChunkName: "confirm-contact" */ "../views/ConfirmContactView.vue"
),
},
{
path: "/contact-amounts",
name: "contact-amounts",
component: () =>
import(
/* webpackChunkName: "contact-amounts" */ "../views/ContactAmountsView.vue"
),
},
{
path: "/contacts",
name: "contacts",
@ -93,12 +95,6 @@ const routes: Array<RouteRecordRaw> = [
/* webpackChunkName: "new-edit-commitment" */ "../views/NewEditCommitmentView.vue"
),
},
{
path: "/project",
name: "project",
component: () =>
import(/* webpackChunkName: "project" */ "../views/ProjectViewView.vue"),
},
{
path: "/new-edit-project",
name: "new-edit-project",
@ -107,6 +103,12 @@ const routes: Array<RouteRecordRaw> = [
/* webpackChunkName: "new-edit-project" */ "../views/NewEditProjectView.vue"
),
},
{
path: "/project",
name: "project",
component: () =>
import(/* webpackChunkName: "project" */ "../views/ProjectViewView.vue"),
},
{
path: "/projects",
name: "projects",
@ -114,12 +116,10 @@ const routes: Array<RouteRecordRaw> = [
import(/* webpackChunkName: "projects" */ "../views/ProjectsView.vue"),
},
{
path: "/commitments",
name: "commitments",
path: "/start",
name: "start",
component: () =>
import(
/* webpackChunkName: "commitments" */ "../views/CommitmentsView.vue"
),
import(/* webpackChunkName: "start" */ "../views/StartView.vue"),
},
];

12
src/test/index.ts

@ -1,9 +1,10 @@
import axios from "axios";
import * as didJwt from "did-jwt";
import { AppString } from "@/constants/app";
import { accountsDB } from "../db";
import { db } from "../db";
import { SERVICE_ID } from "../libs/veramo/setup";
import { deriveAddress, newIdentifier } from "../libs/crypto";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
export async function testServerRegisterUser() {
const testUser0Mnem =
@ -13,9 +14,8 @@ export async function testServerRegisterUser() {
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const thisIdentity = JSON.parse(accounts[0].identity);
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
// Make a claim
const vcClaim = {
@ -23,7 +23,7 @@ export async function testServerRegisterUser() {
"@type": "RegisterAction",
agent: { did: identity0.did },
object: SERVICE_ID,
participant: { did: thisIdentity.did },
participant: { did: settings?.activeDid },
};
// Make a payload for the claim
const vcPayload = {
@ -56,5 +56,5 @@ export async function testServerRegisterUser() {
};
const resp = await axios.post(url, payload, { headers });
console.log("Result:", resp);
console.log("User registration result:", resp);
}

105
src/views/AccountViewView.vue

@ -91,8 +91,8 @@
class="text-sm text-slate-500 flex justify-between items-center mb-1"
>
<span
><code>{{ address }}</code>
<button @click="copy(address)">
><code>{{ activeDid }}</code>
<button @click="copy(activeDid)">
<fa icon="copy" class="text-slate-400 fa-fw ml-1"></fa>
</button>
</span>
@ -202,11 +202,8 @@
</button>
</div>
<div class="flex">
<button
class="text-center text-md text-blue-500 px-1.5 py-2"
@click="checkLimits()"
>
<div class="flex py-2">
<button class="text-center text-md text-blue-500" @click="checkLimits()">
Check Limits
</button>
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9">
@ -225,6 +222,15 @@
</div>
</div>
<div v-if="numAccounts > 0" class="flex py-2">
Switch Account
<span v-for="accountNum in numAccounts" :key="accountNum">
<button class="text-blue-500 px-2" @click="switchAccount(accountNum)">
#{{ accountNum }}
</button>
</span>
</div>
<div v-bind:class="computedAlertClassNames()">
<button
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
@ -240,8 +246,11 @@
<script lang="ts">
import "dexie-export-import";
import * as R from "ramda";
import { Options, Vue } from "vue-class-component";
import { useClipboard } from "@vueuse/core";
import { AppString } from "@/constants/app";
import { db, accountsDB } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import {
@ -250,7 +259,7 @@ import {
generateSeed,
newIdentifier,
} from "@/libs/crypto";
import { AppString } from "@/constants/app";
import { AxiosError } from "axios/index";
//import { testServerRegisterUser } from "../test";
// eslint-disable-next-line @typescript-eslint/no-var-requires
@ -269,11 +278,11 @@ interface RateLimits {
components: {},
})
export default class AccountViewView extends Vue {
address = "";
activeDid = "";
derivationPath = "";
firstName = "";
lastName = "";
mnemonic = "";
numAccounts = 0;
publicHex = "";
publicBase64 = "";
limits: RateLimits | null = null;
@ -296,22 +305,22 @@ export default class AccountViewView extends Vue {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
if (settings) {
this.firstName = settings.firstName || "";
this.lastName = settings.lastName || "";
this.showContactGives = !!settings.showContactGivesInline;
}
this.activeDid = settings?.activeDid || "";
this.firstName = settings?.firstName || "";
this.lastName = settings?.lastName || "";
this.showContactGives = !!settings?.showContactGivesInline;
await accountsDB.open();
const numAccounts = await accountsDB.accounts.count();
if (numAccounts === 0) {
this.numAccounts = await accountsDB.accounts.count();
if (this.numAccounts === 0) {
let address = ""; // 0x... ETH address, without "did:eth:"
let privateHex = "";
this.mnemonic = generateSeed();
[this.address, privateHex, this.publicHex, this.derivationPath] =
deriveAddress(this.mnemonic);
const mnemonic = generateSeed();
[address, privateHex, this.publicHex, this.derivationPath] =
deriveAddress(mnemonic);
const newId = newIdentifier(
this.address,
address,
this.publicHex,
privateHex,
this.derivationPath
@ -319,18 +328,24 @@ export default class AccountViewView extends Vue {
await accountsDB.accounts.add({
dateCreated: new Date().toISOString(),
derivationPath: this.derivationPath,
did: newId.did,
identity: JSON.stringify(newId),
mnemonic: this.mnemonic,
mnemonic: mnemonic,
publicKeyHex: newId.keys[0].publicKeyHex,
});
this.activeDid = newId.did;
}
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
this.address = identity.did;
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "undefined");
this.publicHex = identity.keys[0].publicKeyHex;
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
this.derivationPath = identity.keys[0].meta.derivationPath;
db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: identity.did,
});
} catch (err) {
this.alertMessage =
"Clear your cache and start over (after data backup). See console log for more info.";
@ -344,12 +359,9 @@ export default class AccountViewView extends Vue {
this.showContactGives = !this.showContactGives;
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
if (settings) {
db.settings.update(MASTER_SETTINGS_KEY, {
showContactGivesInline: this.showContactGives,
});
}
} catch (err) {
this.alertMessage =
"Clear your cache and start over (after data backup). See console log for more info.";
@ -387,7 +399,8 @@ export default class AccountViewView extends Vue {
const url = endorserApiServer + "/api/report/rateLimits";
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "undefined");
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
@ -396,23 +409,41 @@ export default class AccountViewView extends Vue {
try {
const resp = await this.axios.get(url, { headers });
// axios throws an exception on a 400
if (resp.status === 200) {
this.limits = resp.data;
} else {
}
} catch (error: unknown) {
const serverError = error as AxiosError;
this.alertTitle = "Error from Server";
console.log("Bad response retrieving limits: ", resp.data);
if (resp.data.error?.message) {
this.alertMessage = resp.data.error?.message;
console.log("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;
if (data.error.message) {
this.alertMessage = data.error.message;
} else {
this.alertMessage = "Bad server response of " + resp.status;
this.alertMessage = "Bad server response. See logs for details.";
}
this.isAlertVisible = true;
}
} catch (err) {
this.alertTitle = "Error from Server";
this.alertMessage = err as string;
this.isAlertVisible = true;
}
async switchAccount(accountNum: number) {
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const account = accounts[accountNum - 1];
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: account.did,
});
this.activeDid = account.did;
this.derivationPath = account.derivationPath;
this.publicHex = account.publicKeyHex;
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
}
public showContactGivesClassNames() {

3
src/views/CommitmentsView.vue

@ -1,3 +0,0 @@
<template>
<section id="Content" class="p-6 pb-24"></section>
</template>

384
src/views/ContactAmountsView.vue

@ -0,0 +1,384 @@
<template>
<!-- QUICK NAV -->
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200">
<ul class="flex text-2xl p-2 gap-2">
<!-- Home Feed -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link :to="{ name: 'home' }" class="block text-center py-3 px-1"
><fa icon="house-chimney" class="fa-fw"></fa
></router-link>
</li>
<!-- Search -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'discover' }"
class="block text-center py-3 px-1"
><fa icon="magnifying-glass" class="fa-fw"></fa
></router-link>
</li>
<!-- Contacts -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'projects' }"
class="block text-center py-3 px-1"
><fa icon="folder-open" class="fa-fw"></fa
></router-link>
</li>
<!-- Contacts -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'contacts' }"
class="block text-center py-3 px-1"
><fa icon="users" class="fa-fw"></fa
></router-link>
</li>
<!-- Profile -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'account' }"
class="block text-center py-3 px-1"
><fa icon="circle-user" class="fa-fw"></fa
></router-link>
</li>
</ul>
</nav>
<section id="Content" class="p-6 pb-24">
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Given with {{ contact?.name }}
</h1>
<!-- Results List -->
<div>
<div class="border-b border-slate-300 flex">
<div class="w-1/4"></div>
<div class="w-1/4">from them</div>
<div class="w-1/4"></div>
<div class="w-1/4">to them</div>
</div>
<div
class="border-b border-slate-300 flex"
v-for="record in giveRecords"
:key="record.id"
>
<div class="w-1/4">
{{ new Date(record.issuedAt).toLocaleString() }}
</div>
<div class="w-1/4">
<span v-if="record.agentDid == contact.did">
<div class="font-bold">
{{ record.amount }} {{ record.unit }}
<span v-if="record.amountConfirmed" class="tooltip">
<fa icon="circle-check" class="text-green-600 fa-fw ml-1" />
<span class="tooltiptext">Confirmed</span>
</span>
<button v-else class="tooltip" @click="confirm(record)">
<fa icon="circle" class="text-blue-600 fa-fw ml-1" />
<span class="tooltiptext">Unconfirmed</span>
</button>
</div>
<br />
{{ record.description }}
</span>
</div>
<div class="w-1/8">
<span v-if="record.agentDid == contact.did">
<fa icon="long-arrow-alt-left" class="text-slate-900 fa-fw ml-1" />
</span>
<span v-else>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<fa icon="long-arrow-alt-right" class="text-slate-900 fa-fw ml-1" />
</span>
</div>
<div class="w-1/4">
<span v-if="record.agentDid != contact.did">
<div class="font-bold">
{{ record.amount }} {{ record.unit }}
<span v-if="record.amountConfirmed" class="tooltip">
<fa icon="circle-check" class="text-green-600 fa-fw ml-1" />
<span class="tooltiptext">Confirmed</span>
</span>
<button v-else class="tooltip" @click="cannotConfirmMessage()">
<fa icon="circle" class="text-slate-600 fa-fw ml-1" />
<span class="tooltiptext">Unconfirmed</span>
</button>
</div>
<br />
{{ record.description }}
</span>
</div>
</div>
</div>
<div v-bind:class="computedAlertClassNames()">
<button
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
@click="onClickClose()"
>
<fa icon="xmark"></fa>
</button>
<h4 class="font-bold pr-5">{{ alertTitle }}</h4>
<p>{{ alertMessage }}</p>
</div>
</section>
</template>
<script lang="ts">
import * as R from "ramda";
import { Options, Vue } from "vue-class-component";
import { accountsDB, db } from "@/db";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { AppString } from "@/constants/app";
import { accessToken, SimpleSigner } from "@/libs/crypto";
import {
AgreeVerifiableCredential,
GiveServerRecord, GiveVerifiableCredential,
SCHEMA_ORG_CONTEXT,
} from "@/libs/endorserServer";
import * as didJwt from "did-jwt";
import { AxiosError } from "axios";
@Options({})
export default class ContactsView extends Vue {
activeDid = "";
contact: Contact | null = null;
giveRecords: Array<GiveServerRecord> = [];
// 'created' hook runs when the Vue instance is first created
async created() {
await db.open();
const contactDid = this.$route.query.contactDid as string;
this.contact = (await db.contacts.get(contactDid)) || null;
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = settings?.activeDid || "";
if (this.activeDid && this.contact) {
this.loadGives(this.activeDid, this.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 || "undefined");
const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER;
// load all the time I have given to them
try {
let result = [];
const url =
endorserApiServer +
"/api/v2/report/gives?agentDid=" +
encodeURIComponent(identity.did) +
"&recipientDid=" +
encodeURIComponent(contact.did);
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
const resp = await this.axios.get(url, { headers });
if (resp.status === 200) {
result = resp.data.data;
} else {
console.log(
"Got bad response status & data of",
resp.status,
resp.data
);
this.alertTitle = "Error With Server";
this.alertMessage =
"Got an error retrieving your given time from the server.";
this.isAlertVisible = true;
}
const url2 =
endorserApiServer +
"/api/v2/report/gives?agentDid=" +
encodeURIComponent(contact.did) +
"&recipientDid=" +
encodeURIComponent(identity.did);
const token2 = await accessToken(identity);
const headers2 = {
"Content-Type": "application/json",
Authorization: "Bearer " + token2,
};
const resp2 = await this.axios.get(url2, { headers: headers2 });
if (resp2.status === 200) {
result = R.concat(result, resp2.data.data);
} else {
console.log(
"Got bad response status & data of",
resp2.status,
resp2.data
);
this.alertTitle = "Error With Server";
this.alertMessage =
"Got an error retrieving your given time from the server.";
this.isAlertVisible = true;
}
const sortedResult: Array<GiveServerRecord> = R.sort(
(a, b) =>
new Date(b.issuedAt).getTime() - new Date(a.issuedAt).getTime(),
result
);
this.giveRecords = sortedResult;
} catch (error) {
this.alertTitle = "Error With Server";
this.alertMessage = error as string;
this.isAlertVisible = true;
}
}
async confirm(record: GiveServerRecord) {
// Make claim
// I use clone here because otherwise it gets a Proxy object.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const origClaim: GiveVerifiableCredential =
R.clone(record.fullClaim);
if (record.fullClaim["@context"] == SCHEMA_ORG_CONTEXT) {
delete origClaim["@context"];
}
origClaim["identifier"] = record.handleId;
const vcClaim: AgreeVerifiableCredential = {
"@context": SCHEMA_ORG_CONTEXT,
"@type": "AgreeAction",
object: origClaim,
};
// Make a payload for the claim
const vcPayload = {
vc: {
"@context": ["https://www.w3.org/2018/credentials/v1"],
type: ["VerifiableCredential"],
credentialSubject: vcClaim,
},
};
// Create a signature using private key of identity
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 || "undefined");
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 endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER;
const url = endorserApiServer + "/api/v2/claim";
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
try {
const resp = await this.axios.post(url, payload, { headers });
//console.log("Got resp data:", resp.data);
if (resp.data?.success) {
record.amountConfirmed = origClaim.object?.amountOfThisGood || 1;
}
} catch (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.alertTitle = "Error With Server";
this.alertMessage = userMessage;
this.isAlertVisible = true;
}
}
}
cannotConfirmMessage() {
this.alertTitle = "Not Allowed";
this.alertMessage = "Only the recipient can confirm final receipt.";
this.isAlertVisible = true;
}
alertTitle = "";
alertMessage = "";
isAlertVisible = false;
public onClickClose() {
this.isAlertVisible = false;
this.alertTitle = "";
this.alertMessage = "";
}
public computedAlertClassNames() {
return {
hidden: !this.isAlertVisible,
"dismissable-alert": true,
"bg-slate-100": true,
"p-5": true,
rounded: true,
"drop-shadow-lg": true,
absolute: true,
"top-3": true,
"inset-x-3": true,
"transition-transform": true,
"ease-in": true,
"duration-300": true,
};
}
}
</script>
<style>
/* Tooltip from https://www.w3schools.com/css/css_tooltip.asp */
/* Tooltip container */
.tooltip {
position: relative;
display: inline-block;
border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
}
/* Tooltip text */
.tooltip .tooltiptext {
visibility: hidden;
width: 200px;
background-color: black;
color: #fff;
text-align: center;
padding: 5px 0;
border-radius: 6px;
position: absolute;
z-index: 1;
}
/* Show the tooltip text when you mouse over the tooltip container */
.tooltip:hover .tooltiptext {
visibility: visible;
}
.tooltip:hover .tooltiptext-left {
visibility: visible;
}
</style>

286
src/views/ContactsView.vue

@ -91,7 +91,7 @@
>
{{
showGiveTotals
? "Totals"
? "Total"
: showGiveConfirmed
? "Confirmed"
: "Unconfirmed"
@ -122,10 +122,10 @@
@click="setVisibility(contact, false)"
>
<fa icon="eye" class="text-slate-900 fa-fw ml-1" />
<span class="tooltiptext">Can see you</span>
<span class="tooltiptext">They can see you</span>
</button>
<button v-else class="tooltip" @click="setVisibility(contact, true)">
<span class="tooltiptext">Cannot see you</span>
<span class="tooltiptext">They cannot see you</span>
<fa icon="eye-slash" class="text-slate-900 fa-fw ml-1" />
</button>
@ -139,7 +139,7 @@
<fa icon="person-circle-check" class="text-slate-900 fa-fw ml-1" />
</button>
<button v-else @click="register(contact)" class="tooltip">
<span class="tooltiptext">Maybe not registered</span>
<span class="tooltiptext">Registration Unknown</span>
<fa
icon="person-circle-question"
class="text-slate-900 fa-fw ml-1"
@ -165,18 +165,18 @@
: (givenByMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */
}}
<span class="tooltiptext-left">{{
givenByMeDescriptions[contact.did]
}}</span>
<span class="tooltiptext-left">
{{ givenByMeDescriptions[contact.did] || "Nothing" }}
</span>
<button
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
@click="onClickAddGive(identity.did, contact.did)"
@click="onClickAddGive(activeDid, contact.did)"
>
+
</button>
</div>
<div class="tooltip px-2">
by:
from:
{{
/* eslint-disable prettier/prettier */
this.showGiveTotals
@ -188,21 +188,32 @@
/* eslint-enable prettier/prettier */
}}
<span class="tooltiptext-left">
{{ givenToMeDescriptions[contact.did] }}
{{ givenToMeDescriptions[contact.did] || "Nothing" }}
</span>
<button
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
@click="onClickAddGive(contact.did, identity.did)"
@click="onClickAddGive(contact.did, activeDid)"
>
+
</button>
</div>
<router-link
:to="{
name: 'contact-amounts',
query: { contactDid: contact.did },
}"
class="tooltip"
>
<fa icon="file-lines" class="text-slate-600 fa-fw ml-1" />
<span class="tooltiptext-left">See All Given Activity</span>
</router-link>
</div>
</div>
</div>
</li>
</ul>
</section>
<div v-bind:class="computedAlertClassNames()">
<button
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
@ -223,48 +234,25 @@ import { IIdentifier } from "@veramo/core";
import { Options, Vue } from "vue-class-component";
import { AppString } from "@/constants/app";
import { accessToken, SimpleSigner } from "@/libs/crypto";
import { accountsDB, db } from "@/db";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken, SimpleSigner } from "@/libs/crypto";
import {
GiveServerRecord,
GiveVerifiableCredential,
RegisterVerifiableCredential,
SERVICE_ID,
} from "@/libs/endorserServer";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
const SERVICE_ID = "endorser.ch";
export interface GiveServerRecord {
agentDid: string;
amount: number;
confirmed: number;
description: string;
fullClaim: GiveVerifiableCredential;
handleId: string;
recipientDid: string;
unit: string;
}
export interface GiveVerifiableCredential {
"@context": string;
"@type": string;
agent: { identifier: string };
description?: string;
object: { amountOfThisGood: number; unitCode: string };
recipient: { identifier: string };
}
export interface RegisterVerifiableCredential {
"@context": string;
"@type": string;
agent: { identifier: string };
object: string;
recipient: { identifier: string };
}
@Options({
components: {},
})
export default class ContactsView extends Vue {
activeDid = "";
contacts: Array<Contact> = [];
contactInput = "";
// { "did:...": concatenated-descriptions } entry for each contact
@ -281,19 +269,16 @@ export default class ContactsView extends Vue {
givenToMeUnconfirmed: Record<string, number> = {};
hourDescriptionInput = "";
hourInput = "0";
identity: IIdentifier | null = null;
showGiveNumbers = false;
showGiveTotals = true;
showGiveConfirmed = true;
// 'created' hook runs when the Vue instance is first created
async created() {
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
this.identity = JSON.parse(accounts[0].identity);
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = settings?.activeDid || "";
this.showGiveNumbers = !!settings?.showContactGivesInline;
if (this.showGiveNumbers) {
this.loadGives();
@ -305,35 +290,13 @@ export default class ContactsView extends Vue {
);
}
async onClickNewContact(): Promise<void> {
let did = this.contactInput;
let name, publicKeyBase64;
const commaPos1 = this.contactInput.indexOf(",");
if (commaPos1 > -1) {
did = this.contactInput.substring(0, commaPos1).trim();
name = this.contactInput.substring(commaPos1 + 1).trim();
const commaPos2 = this.contactInput.indexOf(",", commaPos1 + 1);
if (commaPos2 > -1) {
name = this.contactInput.substring(commaPos1 + 1, commaPos2).trim();
publicKeyBase64 = this.contactInput.substring(commaPos2 + 1).trim();
}
}
// help with potential mistakes while this sharing requires copy-and-paste
if (publicKeyBase64 && /^[0-9A-Fa-f]{66}$/i.test(publicKeyBase64)) {
// it must be all hex (compressed public key), so convert
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
}
const newContact = { did, name, publicKeyBase64 };
await db.contacts.add(newContact);
const allContacts = this.contacts.concat([newContact]);
this.contacts = R.sort(
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
allContacts
);
}
async loadGives() {
if (!this.identity) {
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 || "undefined");
if (!identity) {
console.error(
"Attempted to load Give records with no identity available."
);
@ -346,15 +309,15 @@ export default class ContactsView extends Vue {
try {
const url =
endorserApiServer +
"/api/v2/report/gives?agentId=" +
encodeURIComponent(this.identity?.did);
const token = await accessToken(this.identity);
"/api/v2/report/gives?agentDid=" +
encodeURIComponent(identity.did);
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
const resp = await this.axios.get(url, { headers });
console.log("All your gifts:", resp.data);
console.log("All gifts you've given:", resp.data);
if (resp.status === 200) {
const contactDescriptions: Record<string, string> = {};
const contactConfirmed: Record<string, number> = {};
@ -363,24 +326,36 @@ export default class ContactsView extends Vue {
for (const give of allData) {
if (give.unit == "HUR") {
const recipDid: string = give.recipientDid;
if (give.confirmed) {
if (give.amountConfirmed) {
const prevAmount = contactConfirmed[recipDid] || 0;
contactConfirmed[recipDid] = prevAmount + give.amount;
} else {
const prevAmount = contactUnconfirmed[recipDid] || 0;
contactUnconfirmed[recipDid] = prevAmount + give.amount;
}
const prevDesc = contactDescriptions[recipDid] || "";
// Since many make the tooltip too big, we'll just use the latest;
contactDescriptions[recipDid] = give.description || prevDesc;
if (!contactDescriptions[recipDid] && give.description) {
// Since many make the tooltip too big, we'll just use the latest.
contactDescriptions[recipDid] = give.description;
}
}
}
//console.log("Done retrieving gives", contactConfirmed);
this.givenByMeDescriptions = contactDescriptions;
this.givenByMeConfirmed = contactConfirmed;
this.givenByMeUnconfirmed = contactUnconfirmed;
} else {
console.log(
"Got bad response status & data of",
resp.status,
resp.data
);
this.alertTitle = "Error With Server";
this.alertMessage =
"Got an error retrieving your given time from the server.";
this.isAlertVisible = true;
}
} catch (error) {
this.alertTitle = "Error from Server";
this.alertTitle = "Error With Server";
this.alertMessage = error as string;
this.isAlertVisible = true;
}
@ -389,9 +364,9 @@ export default class ContactsView extends Vue {
try {
const url =
endorserApiServer +
"/api/v2/report/gives?recipientId=" +
encodeURIComponent(this.identity.did);
const token = await accessToken(this.identity);
"/api/v2/report/gives?recipientDid=" +
encodeURIComponent(identity.did);
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
@ -405,29 +380,68 @@ export default class ContactsView extends Vue {
const allData: Array<GiveServerRecord> = resp.data.data;
for (const give of allData) {
if (give.unit == "HUR") {
if (give.confirmed) {
if (give.amountConfirmed) {
const prevAmount = contactConfirmed[give.agentDid] || 0;
contactConfirmed[give.agentDid] = prevAmount + give.amount;
} else {
const prevAmount = contactUnconfirmed[give.agentDid] || 0;
contactUnconfirmed[give.agentDid] = prevAmount + give.amount;
}
const prevDesc = contactDescriptions[give.agentDid] || "";
// Since many make the tooltip too big, we'll just use the latest;
contactDescriptions[give.agentDid] = give.description || prevDesc;
if (!contactDescriptions[give.agentDid] && give.description) {
// Since many make the tooltip too big, we'll just use the latest.
contactDescriptions[give.agentDid] = give.description;
}
}
}
//console.log("Done retrieving receipts", contactConfirmed);
this.givenToMeDescriptions = contactDescriptions;
this.givenToMeConfirmed = contactConfirmed;
this.givenToMeUnconfirmed = contactUnconfirmed;
} else {
console.log(
"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.";
this.isAlertVisible = true;
}
} catch (error) {
this.alertTitle = "Error from Server";
this.alertTitle = "Error With Server";
this.alertMessage = error as string;
this.isAlertVisible = true;
}
}
async onClickNewContact(): Promise<void> {
let did = this.contactInput;
let name, publicKeyBase64;
const commaPos1 = this.contactInput.indexOf(",");
if (commaPos1 > -1) {
did = this.contactInput.substring(0, commaPos1).trim();
name = this.contactInput.substring(commaPos1 + 1).trim();
const commaPos2 = this.contactInput.indexOf(",", commaPos1 + 1);
if (commaPos2 > -1) {
name = this.contactInput.substring(commaPos1 + 1, commaPos2).trim();
publicKeyBase64 = this.contactInput.substring(commaPos2 + 1).trim();
}
}
// help with potential mistakes while this sharing requires copy-and-paste
if (publicKeyBase64 && /^[0-9A-Fa-f]{66}$/i.test(publicKeyBase64)) {
// it must be all hex (compressed public key), so convert
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
}
const newContact = { did, name, publicKeyBase64 };
await db.contacts.add(newContact);
const allContacts = this.contacts.concat([newContact]);
this.contacts = R.sort(
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
allContacts
);
}
async deleteContact(contact: Contact) {
if (
confirm(
@ -454,7 +468,9 @@ export default class ContactsView extends Vue {
) {
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "undefined");
// Make a claim
const vcClaim: RegisterVerifiableCredential = {
"@context": "https://schema.org",
@ -518,7 +534,7 @@ export default class ContactsView extends Vue {
userMessage = error as string;
}
// Now set that error for the user to see.
this.alertTitle = "Error with Server";
this.alertTitle = "Error With Server";
this.alertMessage = userMessage;
this.isAlertVisible = true;
}
@ -534,7 +550,9 @@ export default class ContactsView extends Vue {
(visibility ? "canSeeMe" : "cannotSeeMe");
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "undefined");
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
@ -548,7 +566,7 @@ export default class ContactsView extends Vue {
contact.seesMe = visibility;
db.contacts.update(contact.did, { seesMe: visibility });
} else {
this.alertTitle = "Error from Server";
this.alertTitle = "Error With Server";
console.log("Bad response setting visibility: ", resp.data);
if (resp.data.error?.message) {
this.alertMessage = resp.data.error?.message;
@ -558,7 +576,7 @@ export default class ContactsView extends Vue {
this.isAlertVisible = true;
}
} catch (err) {
this.alertTitle = "Error from Server";
this.alertTitle = "Error With Server";
this.alertMessage = err as string;
this.isAlertVisible = true;
}
@ -572,7 +590,9 @@ export default class ContactsView extends Vue {
encodeURIComponent(contact.did);
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "undefined");
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
@ -594,7 +614,7 @@ export default class ContactsView extends Vue {
"see your activity.";
this.isAlertVisible = true;
} else {
this.alertTitle = "Error from Server";
this.alertTitle = "Error With Server";
if (resp.data.error?.message) {
this.alertMessage = resp.data.error?.message;
} else {
@ -603,7 +623,7 @@ export default class ContactsView extends Vue {
this.isAlertVisible = true;
}
} catch (err) {
this.alertTitle = "Error from Server";
this.alertTitle = "Error With Server";
this.alertMessage = err as string;
this.isAlertVisible = true;
}
@ -625,6 +645,27 @@ export default class ContactsView extends Vue {
}
async onClickAddGive(fromDid: string, toDid: string): Promise<void> {
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 || "undefined");
// if they have unconfirmed amounts, ask to confirm those first
if (toDid == identity?.did && this.givenToMeUnconfirmed[fromDid] > 0) {
if (
confirm(
"There are " +
this.givenToMeUnconfirmed[fromDid] +
" unconfirmed hours from them." +
" Would you like to confirm some of those hours?"
)
) {
this.$router.push({
name: "contact-amounts",
query: { contactDid: fromDid },
});
}
}
if (!this.isNumeric(this.hourInput)) {
this.alertTitle = "Input Error";
this.alertMessage =
@ -634,13 +675,14 @@ export default class ContactsView extends Vue {
this.alertTitle = "Input Error";
this.alertMessage = "Giving 0 hours does nothing.";
this.isAlertVisible = true;
} else if (!this.identity) {
} else if (!identity) {
this.alertTitle = "Status Error";
this.alertMessage = "No identity is available.";
this.isAlertVisible = true;
} else {
// ask to confirm amount
let toFrom;
if (fromDid == this.identity?.did) {
if (fromDid == identity?.did) {
toFrom = "from you to " + this.nameForDid(this.contacts, toDid);
} else {
toFrom = "from " + this.nameForDid(this.contacts, fromDid) + " to you";
@ -662,7 +704,7 @@ export default class ContactsView extends Vue {
)
) {
this.createAndSubmitGive(
this.identity,
identity,
fromDid,
toDid,
parseFloat(this.hourInput),
@ -726,20 +768,17 @@ export default class ContactsView extends Vue {
const resp = await this.axios.post(url, payload, { headers });
//console.log("Got resp data:", resp.data);
if (resp.data?.success?.handleId) {
this.alertTitle = "";
this.alertMessage = "";
this.alertTitle = "Done";
this.alertMessage = "Successfully logged time to the server.";
this.isAlertVisible = true;
if (fromDid === identity.did) {
this.givenByMeConfirmed[toDid] =
this.givenByMeConfirmed[toDid] + amount;
// do this to update the UI (is there a better way?)
// eslint-disable-next-line no-self-assign
this.givenByMeConfirmed = this.givenByMeConfirmed;
const newList = R.clone(this.givenByMeUnconfirmed);
newList[toDid] = (newList[toDid] || 0) + amount;
this.givenByMeUnconfirmed = newList;
} else {
this.givenToMeConfirmed[fromDid] =
this.givenToMeConfirmed[fromDid] + amount;
// do this to update the UI (is there a better way?)
// eslint-disable-next-line no-self-assign
this.givenToMeConfirmed = this.givenToMeConfirmed;
const newList = R.clone(this.givenToMeConfirmed);
newList[fromDid] = (newList[fromDid] || 0) + amount;
this.givenToMeConfirmed = newList;
}
}
} catch (error) {
@ -755,26 +794,13 @@ export default class ContactsView extends Vue {
userMessage = error as string;
}
// Now set that error for the user to see.
this.alertTitle = "Error with Server";
this.alertTitle = "Error With Server";
this.alertMessage = userMessage;
this.isAlertVisible = true;
}
}
}
public selectedGiveTotal(
contactGivesConfirmed: Record<string, number>,
contactGivesUnconfirmed: Record<string, number>,
did: string
) {
/* eslint-disable prettier/prettier */
this.showGiveTotals
? ((contactGivesConfirmed[did] || 0) + (contactGivesUnconfirmed[did] || 0))
: this.showGiveConfirmed
? (contactGivesConfirmed[did] || 0)
: (contactGivesUnconfirmed[did] || 0);
/* eslint-enable prettier/prettier */
}
public toggleShowGiveTotals() {
if (this.showGiveTotals) {
this.showGiveTotals = false;
@ -817,7 +843,7 @@ export default class ContactsView extends Vue {
public showGiveAmountsClassNames() {
return {
"bg-slate-900": this.showGiveTotals,
"bg-slate-500": this.showGiveTotals,
"bg-green-600": !this.showGiveTotals && this.showGiveConfirmed,
"bg-yellow-600": !this.showGiveTotals && !this.showGiveConfirmed,
};

13
src/views/ImportAccountView.vue

@ -45,7 +45,8 @@
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { deriveAddress, newIdentifier } from "../libs/crypto";
import { accountsDB } from "@/db";
import { accountsDB, db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Options({
components: {},
@ -76,16 +77,20 @@ export default class ImportAccountView extends Vue {
try {
await accountsDB.open();
const num_accounts = await accountsDB.accounts.count();
if (num_accounts === 0) {
await accountsDB.accounts.add({
dateCreated: new Date().toISOString(),
derivationPath: this.derivationPath,
did: newId.did,
identity: JSON.stringify(newId),
mnemonic: mne,
publicKeyHex: newId.keys[0].publicKeyHex,
});
}
// record that as the active DID
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: newId.did,
});
this.$router.push({ name: "account" });
} catch (err) {
console.log("Error!");

6
src/views/NewEditAccountView.vue

@ -70,10 +70,8 @@ export default class NewEditAccountView extends Vue {
async created() {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
if (settings) {
this.firstName = settings.firstName || "";
this.lastName = settings.lastName || "";
}
this.firstName = settings?.firstName || "";
this.lastName = settings?.lastName || "";
}
onClickSaveChanges() {

26
src/views/NewEditProjectView.vue

@ -76,14 +76,17 @@
</template>
<script lang="ts">
import { AxiosError } from "axios";
import * as didJwt from "did-jwt";
import * as R from "ramda";
import { Options, Vue } from "vue-class-component";
import { AppString } from "@/constants/app";
import { accountsDB } from "../db";
import { accountsDB, db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken, SimpleSigner } from "@/libs/crypto";
import * as didJwt from "did-jwt";
import { IIdentifier } from "@veramo/core";
import { useAppStore } from "@/store/app";
import { AxiosError } from "axios";
import { IIdentifier } from "@veramo/core";
interface VerifiableCredential {
"@context": string;
@ -97,6 +100,7 @@ interface VerifiableCredential {
components: {},
})
export default class NewEditProjectView extends Vue {
activeDid = "";
projectName = "";
description = "";
errorMessage = "";
@ -109,16 +113,19 @@ export default class NewEditProjectView extends Vue {
// 'created' hook runs when the Vue instance is first created
async created() {
if (this.projectId === "") {
console.log("This is a new project");
} else {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = settings?.activeDid || "";
if (this.projectId) {
await accountsDB.open();
const num_accounts = await accountsDB.accounts.count();
if (num_accounts === 0) {
console.log("Problem! Should have a profile!");
} else {
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "undefined");
this.LoadProject(identity);
}
}
@ -257,7 +264,8 @@ export default class NewEditProjectView extends Vue {
console.log("Problem! Should have a profile!");
} else {
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
const account = R.find((acc) => acc.did === this.activeDid, accounts);
const identity = JSON.parse(account?.identity || "undefined");
this.SaveProject(identity);
}
}

18
src/views/ProjectViewView.vue

@ -146,13 +146,16 @@
</template>
<script lang="ts">
import { AxiosError } from "axios";
import * as moment from "moment";
import * as R from "ramda";
import { Options, Vue } from "vue-class-component";
import { AppString } from "@/constants/app";
import { accountsDB, db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import { accountsDB } from "../db";
import { IIdentifier } from "@veramo/core";
import { AppString } from "@/constants/app";
import * as moment from "moment";
import { AxiosError } from "axios";
@Options({
components: {},
@ -226,13 +229,18 @@ export default class ProjectViewView extends Vue {
// 'created' hook runs when the Vue instance is first created
async created() {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const activeDid = settings?.activeDid || "";
await accountsDB.open();
const num_accounts = await accountsDB.accounts.count();
if (num_accounts === 0) {
console.log("Problem! Should have a profile!");
} else {
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
const account = R.find((acc) => acc.did === activeDid, accounts);
const identity = JSON.parse(account?.identity || "undefined");
this.LoadProject(identity);
}
}

14
src/views/ProjectsView.vue

@ -101,11 +101,14 @@
</template>
<script lang="ts">
import * as R from "ramda";
import { Options, Vue } from "vue-class-component";
import { AppString } from "@/constants/app";
import { accountsDB, db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import { accountsDB } from "../db";
import { IIdentifier } from "@veramo/core";
import { AppString } from "@/constants/app";
@Options({
components: {},
@ -155,13 +158,18 @@ export default class ProjectsView extends Vue {
// 'created' hook runs when the Vue instance is first created
async created() {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const activeDid = settings?.activeDid || "";
await accountsDB.open();
const num_accounts = await accountsDB.accounts.count();
if (num_accounts === 0) {
console.log("Problem! Should have a profile!");
} else {
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
const account = R.find((acc) => acc.did === activeDid, accounts);
const identity = JSON.parse(account?.identity || "undefined");
this.LoadProjects(identity);
}
}

Loading…
Cancel
Save