forked from jsnbuchanan/crowd-funder-for-time-pwa
Merge pull request 'add separate screen for amount confirmations' (#15) from separate-dbs into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#15
This commit is contained in:
15
README.md
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
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,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",
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
@@ -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
Normal file
40
src/libs/endorserServer.ts
Normal file
@@ -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 };
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
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,25 +409,43 @@ 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 {
|
||||
this.alertTitle = "Error from Server";
|
||||
console.log("Bad response retrieving limits: ", resp.data);
|
||||
if (resp.data.error?.message) {
|
||||
this.alertMessage = resp.data.error?.message;
|
||||
} else {
|
||||
this.alertMessage = "Bad server response of " + resp.status;
|
||||
}
|
||||
this.isAlertVisible = true;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (error: unknown) {
|
||||
const serverError = error as AxiosError;
|
||||
|
||||
this.alertTitle = "Error from Server";
|
||||
this.alertMessage = err as string;
|
||||
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. See logs for details.";
|
||||
}
|
||||
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() {
|
||||
return {
|
||||
"bg-slate-900": !this.showContactGives,
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<section id="Content" class="p-6 pb-24"></section>
|
||||
</template>
|
||||
384
src/views/ContactAmountsView.vue
Normal file
384
src/views/ContactAmountsView.vue
Normal file
@@ -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>
|
||||
|
||||
<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>
|
||||
@@ -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,6 +290,131 @@ export default class ContactsView extends Vue {
|
||||
);
|
||||
}
|
||||
|
||||
async loadGives() {
|
||||
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."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER;
|
||||
|
||||
// load all the time I have given
|
||||
try {
|
||||
const url =
|
||||
endorserApiServer +
|
||||
"/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 gifts you've given:", resp.data);
|
||||
if (resp.status === 200) {
|
||||
const contactDescriptions: Record<string, string> = {};
|
||||
const contactConfirmed: Record<string, number> = {};
|
||||
const contactUnconfirmed: Record<string, number> = {};
|
||||
const allData: Array<GiveServerRecord> = resp.data.data;
|
||||
for (const give of allData) {
|
||||
if (give.unit == "HUR") {
|
||||
const recipDid: string = give.recipientDid;
|
||||
if (give.amountConfirmed) {
|
||||
const prevAmount = contactConfirmed[recipDid] || 0;
|
||||
contactConfirmed[recipDid] = prevAmount + give.amount;
|
||||
} else {
|
||||
const prevAmount = contactUnconfirmed[recipDid] || 0;
|
||||
contactUnconfirmed[recipDid] = prevAmount + give.amount;
|
||||
}
|
||||
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 With Server";
|
||||
this.alertMessage = error as string;
|
||||
this.isAlertVisible = true;
|
||||
}
|
||||
|
||||
// load all the time I have received
|
||||
try {
|
||||
const url =
|
||||
endorserApiServer +
|
||||
"/api/v2/report/gives?recipientDid=" +
|
||||
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 gifts you've recieved:", resp.data);
|
||||
if (resp.status === 200) {
|
||||
const contactDescriptions: Record<string, string> = {};
|
||||
const contactConfirmed: Record<string, number> = {};
|
||||
const contactUnconfirmed: Record<string, number> = {};
|
||||
const allData: Array<GiveServerRecord> = resp.data.data;
|
||||
for (const give of allData) {
|
||||
if (give.unit == "HUR") {
|
||||
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;
|
||||
}
|
||||
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 With Server";
|
||||
this.alertMessage = error as string;
|
||||
this.isAlertVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
async onClickNewContact(): Promise<void> {
|
||||
let did = this.contactInput;
|
||||
let name, publicKeyBase64;
|
||||
@@ -332,102 +442,6 @@ export default class ContactsView extends Vue {
|
||||
);
|
||||
}
|
||||
|
||||
async loadGives() {
|
||||
if (!this.identity) {
|
||||
console.error(
|
||||
"Attempted to load Give records with no identity available."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER;
|
||||
|
||||
// load all the time I have given
|
||||
try {
|
||||
const url =
|
||||
endorserApiServer +
|
||||
"/api/v2/report/gives?agentId=" +
|
||||
encodeURIComponent(this.identity?.did);
|
||||
const token = await accessToken(this.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);
|
||||
if (resp.status === 200) {
|
||||
const contactDescriptions: Record<string, string> = {};
|
||||
const contactConfirmed: Record<string, number> = {};
|
||||
const contactUnconfirmed: Record<string, number> = {};
|
||||
const allData: Array<GiveServerRecord> = resp.data.data;
|
||||
for (const give of allData) {
|
||||
if (give.unit == "HUR") {
|
||||
const recipDid: string = give.recipientDid;
|
||||
if (give.confirmed) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
//console.log("Done retrieving gives", contactConfirmed);
|
||||
this.givenByMeDescriptions = contactDescriptions;
|
||||
this.givenByMeConfirmed = contactConfirmed;
|
||||
}
|
||||
} catch (error) {
|
||||
this.alertTitle = "Error from Server";
|
||||
this.alertMessage = error as string;
|
||||
this.isAlertVisible = true;
|
||||
}
|
||||
|
||||
// load all the time I have received
|
||||
try {
|
||||
const url =
|
||||
endorserApiServer +
|
||||
"/api/v2/report/gives?recipientId=" +
|
||||
encodeURIComponent(this.identity.did);
|
||||
const token = await accessToken(this.identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
const resp = await this.axios.get(url, { headers });
|
||||
console.log("All gifts you've recieved:", resp.data);
|
||||
if (resp.status === 200) {
|
||||
const contactDescriptions: Record<string, string> = {};
|
||||
const contactConfirmed: Record<string, number> = {};
|
||||
const contactUnconfirmed: Record<string, number> = {};
|
||||
const allData: Array<GiveServerRecord> = resp.data.data;
|
||||
for (const give of allData) {
|
||||
if (give.unit == "HUR") {
|
||||
if (give.confirmed) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
//console.log("Done retrieving receipts", contactConfirmed);
|
||||
this.givenToMeDescriptions = contactDescriptions;
|
||||
this.givenToMeConfirmed = contactConfirmed;
|
||||
}
|
||||
} catch (error) {
|
||||
this.alertTitle = "Error from Server";
|
||||
this.alertMessage = error as string;
|
||||
this.isAlertVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
identity: JSON.stringify(newId),
|
||||
mnemonic: mne,
|
||||
publicKeyHex: newId.keys[0].publicKeyHex,
|
||||
});
|
||||
}
|
||||
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!");
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -76,14 +76,17 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
import { AppString } from "@/constants/app";
|
||||
import { accountsDB } from "../db";
|
||||
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 * as didJwt from "did-jwt";
|
||||
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, SimpleSigner } from "@/libs/crypto";
|
||||
import { useAppStore } from "@/store/app";
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,13 +146,16 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
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";
|
||||
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 { IIdentifier } from "@veramo/core";
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,11 +101,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import * as R from "ramda";
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import { accountsDB } from "../db";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
|
||||
import { AppString } from "@/constants/app";
|
||||
import { accountsDB, db } from "@/db";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user