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 was merged in pull request #15.
This commit is contained in:
15
README.md
15
README.md
@@ -22,7 +22,8 @@ npm run lint
|
|||||||
|
|
||||||
### Clear data & restart
|
### 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
|
### Test key contents
|
||||||
|
|
||||||
@@ -30,7 +31,8 @@ See [this page](openssl_signing_console.rst)
|
|||||||
|
|
||||||
### Register new user on test server
|
### 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 = {
|
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:
|
- 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`
|
`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".
|
* In the `src/views/AccountViewView.vue` file, uncomment the lines referring to "testServerRegisterUser".
|
||||||
|
|
||||||
* Visit the `/account` page.
|
* 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
|
### 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)
|
- replace user-affecting console.logs with error messages (eg. catches)
|
||||||
|
|
||||||
- contacts v1 :
|
- contacts v1 :
|
||||||
- test confirmed vs unconfirmed amounts
|
- .1 change "confirmed" flag to "amountConfirmed" on gives
|
||||||
- remove 'copy' until it works
|
- .2 warn about amounts when you cannot see them
|
||||||
- switch to prod server
|
- .1 remove 'copy' until it works
|
||||||
- 01 show gives with confirmations
|
- .5 switch to prod server
|
||||||
- .5 Add page to show seed.
|
- .5 Add page to show seed.
|
||||||
- 01 Provide a way to import the non-sensitive data.
|
- 01 Provide a way to import the non-sensitive data.
|
||||||
- 01 Provide way to share your contact info.
|
- 01 Provide way to share your contact info.
|
||||||
- .2 move all "identity" references to temporary account access
|
- .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+ :
|
- contacts v+ :
|
||||||
- .5 make advanced "show/hide amounts" button into a nice UI toggle
|
- .5 make advanced "show/hide amounts" button into a nice UI toggle
|
||||||
@@ -29,8 +30,10 @@
|
|||||||
|
|
||||||
- refactor UI :
|
- refactor UI :
|
||||||
- .5 Alerts show at the top and can be missed, eg. account data download
|
- .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.
|
- 01 Code for "nav" tabs across the bottom is duplicated on each page.
|
||||||
- .2 Add "copied" feedback when they click "copy" on /account
|
- .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
|
- commit screen
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Generic strings that could be used throughout the app.
|
* Generic strings that could be used throughout the app.
|
||||||
*/
|
*/
|
||||||
export enum AppString {
|
export enum AppString {
|
||||||
APP_NAME = "Kickstart for time",
|
APP_NAME = "KickStart with Time",
|
||||||
VERSION = "0.1",
|
VERSION = "0.1",
|
||||||
DEFAULT_ENDORSER_API_SERVER = "https://test.endorser.ch:8000",
|
DEFAULT_ENDORSER_API_SERVER = "https://test.endorser.ch:8000",
|
||||||
//DEFAULT_ENDORSER_API_SERVER = "http://localhost:3000",
|
//DEFAULT_ENDORSER_API_SERVER = "http://localhost:3000",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export type Account = {
|
|||||||
id?: number; // auto-generated by Dexie
|
id?: number; // auto-generated by Dexie
|
||||||
dateCreated: string;
|
dateCreated: string;
|
||||||
derivationPath: string;
|
derivationPath: string;
|
||||||
|
did: string;
|
||||||
identity: string;
|
identity: string;
|
||||||
publicKeyHex: string;
|
publicKeyHex: string;
|
||||||
mnemonic: 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
|
// see https://github.com/PVermeer/dexie-addon-suite-monorepo/tree/master/packages/dexie-encrypted-addon
|
||||||
export const AccountsSchema = {
|
export const AccountsSchema = {
|
||||||
accounts:
|
accounts:
|
||||||
"++id, dateCreated, derivationPath, $identity, $mnemonic, publicKeyHex",
|
"++id, dateCreated, derivationPath, did, $identity, $mnemonic, publicKeyHex",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// a singleton
|
// a singleton
|
||||||
export type Settings = {
|
export type Settings = {
|
||||||
id: number;
|
id: number; // there's only one entry: MASTER_SETTINGS_KEY
|
||||||
|
activeDid?: string;
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
showContactGivesInline?: boolean;
|
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 {
|
import {
|
||||||
faCalendar,
|
faCalendar,
|
||||||
faChevronLeft,
|
faChevronLeft,
|
||||||
|
faCircle,
|
||||||
faCircleCheck,
|
faCircleCheck,
|
||||||
faCircleQuestion,
|
faCircleQuestion,
|
||||||
faCircleUser,
|
faCircleUser,
|
||||||
@@ -19,9 +20,12 @@ import {
|
|||||||
faEllipsisVertical,
|
faEllipsisVertical,
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
|
faFileLines,
|
||||||
faFolderOpen,
|
faFolderOpen,
|
||||||
faHand,
|
faHand,
|
||||||
faHouseChimney,
|
faHouseChimney,
|
||||||
|
faLongArrowAltLeft,
|
||||||
|
faLongArrowAltRight,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faPen,
|
faPen,
|
||||||
faPersonCircleCheck,
|
faPersonCircleCheck,
|
||||||
@@ -40,6 +44,7 @@ import {
|
|||||||
library.add(
|
library.add(
|
||||||
faCalendar,
|
faCalendar,
|
||||||
faChevronLeft,
|
faChevronLeft,
|
||||||
|
faCircle,
|
||||||
faCircleCheck,
|
faCircleCheck,
|
||||||
faCircleQuestion,
|
faCircleQuestion,
|
||||||
faCircleUser,
|
faCircleUser,
|
||||||
@@ -47,9 +52,12 @@ library.add(
|
|||||||
faEllipsisVertical,
|
faEllipsisVertical,
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
|
faFileLines,
|
||||||
faFolderOpen,
|
faFolderOpen,
|
||||||
faHand,
|
faHand,
|
||||||
faHouseChimney,
|
faHouseChimney,
|
||||||
|
faLongArrowAltLeft,
|
||||||
|
faLongArrowAltRight,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faPen,
|
faPen,
|
||||||
faPersonCircleCheck,
|
faPersonCircleCheck,
|
||||||
|
|||||||
@@ -23,12 +23,6 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
component: () =>
|
component: () =>
|
||||||
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
|
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/start",
|
|
||||||
name: "start",
|
|
||||||
component: () =>
|
|
||||||
import(/* webpackChunkName: "start" */ "../views/StartView.vue"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/account",
|
path: "/account",
|
||||||
name: "account",
|
name: "account",
|
||||||
@@ -43,6 +37,14 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
/* webpackChunkName: "confirm-contact" */ "../views/ConfirmContactView.vue"
|
/* webpackChunkName: "confirm-contact" */ "../views/ConfirmContactView.vue"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/contact-amounts",
|
||||||
|
name: "contact-amounts",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "contact-amounts" */ "../views/ContactAmountsView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/contacts",
|
path: "/contacts",
|
||||||
name: "contacts",
|
name: "contacts",
|
||||||
@@ -93,12 +95,6 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
/* webpackChunkName: "new-edit-commitment" */ "../views/NewEditCommitmentView.vue"
|
/* webpackChunkName: "new-edit-commitment" */ "../views/NewEditCommitmentView.vue"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/project",
|
|
||||||
name: "project",
|
|
||||||
component: () =>
|
|
||||||
import(/* webpackChunkName: "project" */ "../views/ProjectViewView.vue"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/new-edit-project",
|
path: "/new-edit-project",
|
||||||
name: "new-edit-project",
|
name: "new-edit-project",
|
||||||
@@ -107,6 +103,12 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
/* webpackChunkName: "new-edit-project" */ "../views/NewEditProjectView.vue"
|
/* webpackChunkName: "new-edit-project" */ "../views/NewEditProjectView.vue"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/project",
|
||||||
|
name: "project",
|
||||||
|
component: () =>
|
||||||
|
import(/* webpackChunkName: "project" */ "../views/ProjectViewView.vue"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/projects",
|
path: "/projects",
|
||||||
name: "projects",
|
name: "projects",
|
||||||
@@ -114,12 +116,10 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
import(/* webpackChunkName: "projects" */ "../views/ProjectsView.vue"),
|
import(/* webpackChunkName: "projects" */ "../views/ProjectsView.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/commitments",
|
path: "/start",
|
||||||
name: "commitments",
|
name: "start",
|
||||||
component: () =>
|
component: () =>
|
||||||
import(
|
import(/* webpackChunkName: "start" */ "../views/StartView.vue"),
|
||||||
/* webpackChunkName: "commitments" */ "../views/CommitmentsView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import { AppString } from "@/constants/app";
|
import { AppString } from "@/constants/app";
|
||||||
import { accountsDB } from "../db";
|
import { db } from "../db";
|
||||||
import { SERVICE_ID } from "../libs/veramo/setup";
|
import { SERVICE_ID } from "../libs/veramo/setup";
|
||||||
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
||||||
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
|
||||||
export async function testServerRegisterUser() {
|
export async function testServerRegisterUser() {
|
||||||
const testUser0Mnem =
|
const testUser0Mnem =
|
||||||
@@ -13,9 +14,8 @@ export async function testServerRegisterUser() {
|
|||||||
|
|
||||||
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
|
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
|
||||||
|
|
||||||
await accountsDB.open();
|
await db.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
const thisIdentity = JSON.parse(accounts[0].identity);
|
|
||||||
|
|
||||||
// Make a claim
|
// Make a claim
|
||||||
const vcClaim = {
|
const vcClaim = {
|
||||||
@@ -23,7 +23,7 @@ export async function testServerRegisterUser() {
|
|||||||
"@type": "RegisterAction",
|
"@type": "RegisterAction",
|
||||||
agent: { did: identity0.did },
|
agent: { did: identity0.did },
|
||||||
object: SERVICE_ID,
|
object: SERVICE_ID,
|
||||||
participant: { did: thisIdentity.did },
|
participant: { did: settings?.activeDid },
|
||||||
};
|
};
|
||||||
// Make a payload for the claim
|
// Make a payload for the claim
|
||||||
const vcPayload = {
|
const vcPayload = {
|
||||||
@@ -56,5 +56,5 @@ export async function testServerRegisterUser() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resp = await axios.post(url, payload, { headers });
|
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"
|
class="text-sm text-slate-500 flex justify-between items-center mb-1"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
><code>{{ address }}</code>
|
><code>{{ activeDid }}</code>
|
||||||
<button @click="copy(address)">
|
<button @click="copy(activeDid)">
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw ml-1"></fa>
|
<fa icon="copy" class="text-slate-400 fa-fw ml-1"></fa>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
@@ -202,11 +202,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex py-2">
|
||||||
<button
|
<button class="text-center text-md text-blue-500" @click="checkLimits()">
|
||||||
class="text-center text-md text-blue-500 px-1.5 py-2"
|
|
||||||
@click="checkLimits()"
|
|
||||||
>
|
|
||||||
Check Limits
|
Check Limits
|
||||||
</button>
|
</button>
|
||||||
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9">
|
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9">
|
||||||
@@ -225,6 +222,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</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()">
|
<div v-bind:class="computedAlertClassNames()">
|
||||||
<button
|
<button
|
||||||
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
|
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
|
||||||
@@ -240,8 +246,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "dexie-export-import";
|
import "dexie-export-import";
|
||||||
|
import * as R from "ramda";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
|
import { AppString } from "@/constants/app";
|
||||||
import { db, accountsDB } from "@/db";
|
import { db, accountsDB } from "@/db";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import {
|
import {
|
||||||
@@ -250,7 +259,7 @@ import {
|
|||||||
generateSeed,
|
generateSeed,
|
||||||
newIdentifier,
|
newIdentifier,
|
||||||
} from "@/libs/crypto";
|
} from "@/libs/crypto";
|
||||||
import { AppString } from "@/constants/app";
|
import { AxiosError } from "axios/index";
|
||||||
//import { testServerRegisterUser } from "../test";
|
//import { testServerRegisterUser } from "../test";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
@@ -269,11 +278,11 @@ interface RateLimits {
|
|||||||
components: {},
|
components: {},
|
||||||
})
|
})
|
||||||
export default class AccountViewView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
address = "";
|
activeDid = "";
|
||||||
derivationPath = "";
|
derivationPath = "";
|
||||||
firstName = "";
|
firstName = "";
|
||||||
lastName = "";
|
lastName = "";
|
||||||
mnemonic = "";
|
numAccounts = 0;
|
||||||
publicHex = "";
|
publicHex = "";
|
||||||
publicBase64 = "";
|
publicBase64 = "";
|
||||||
limits: RateLimits | null = null;
|
limits: RateLimits | null = null;
|
||||||
@@ -296,22 +305,22 @@ export default class AccountViewView extends Vue {
|
|||||||
try {
|
try {
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
if (settings) {
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.firstName = settings.firstName || "";
|
this.firstName = settings?.firstName || "";
|
||||||
this.lastName = settings.lastName || "";
|
this.lastName = settings?.lastName || "";
|
||||||
this.showContactGives = !!settings.showContactGivesInline;
|
this.showContactGives = !!settings?.showContactGivesInline;
|
||||||
}
|
|
||||||
|
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const numAccounts = await accountsDB.accounts.count();
|
this.numAccounts = await accountsDB.accounts.count();
|
||||||
if (numAccounts === 0) {
|
if (this.numAccounts === 0) {
|
||||||
|
let address = ""; // 0x... ETH address, without "did:eth:"
|
||||||
let privateHex = "";
|
let privateHex = "";
|
||||||
this.mnemonic = generateSeed();
|
const mnemonic = generateSeed();
|
||||||
[this.address, privateHex, this.publicHex, this.derivationPath] =
|
[address, privateHex, this.publicHex, this.derivationPath] =
|
||||||
deriveAddress(this.mnemonic);
|
deriveAddress(mnemonic);
|
||||||
|
|
||||||
const newId = newIdentifier(
|
const newId = newIdentifier(
|
||||||
this.address,
|
address,
|
||||||
this.publicHex,
|
this.publicHex,
|
||||||
privateHex,
|
privateHex,
|
||||||
this.derivationPath
|
this.derivationPath
|
||||||
@@ -319,18 +328,24 @@ export default class AccountViewView extends Vue {
|
|||||||
await accountsDB.accounts.add({
|
await accountsDB.accounts.add({
|
||||||
dateCreated: new Date().toISOString(),
|
dateCreated: new Date().toISOString(),
|
||||||
derivationPath: this.derivationPath,
|
derivationPath: this.derivationPath,
|
||||||
|
did: newId.did,
|
||||||
identity: JSON.stringify(newId),
|
identity: JSON.stringify(newId),
|
||||||
mnemonic: this.mnemonic,
|
mnemonic: mnemonic,
|
||||||
publicKeyHex: newId.keys[0].publicKeyHex,
|
publicKeyHex: newId.keys[0].publicKeyHex,
|
||||||
});
|
});
|
||||||
|
this.activeDid = newId.did;
|
||||||
}
|
}
|
||||||
|
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
const identity = JSON.parse(accounts[0].identity);
|
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||||
this.address = identity.did;
|
const identity = JSON.parse(account?.identity || "undefined");
|
||||||
this.publicHex = identity.keys[0].publicKeyHex;
|
this.publicHex = identity.keys[0].publicKeyHex;
|
||||||
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
|
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
|
||||||
this.derivationPath = identity.keys[0].meta.derivationPath;
|
this.derivationPath = identity.keys[0].meta.derivationPath;
|
||||||
|
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
activeDid: identity.did,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.alertMessage =
|
this.alertMessage =
|
||||||
"Clear your cache and start over (after data backup). See console log for more info.";
|
"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;
|
this.showContactGives = !this.showContactGives;
|
||||||
try {
|
try {
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
|
||||||
if (settings) {
|
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
showContactGivesInline: this.showContactGives,
|
showContactGivesInline: this.showContactGives,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.alertMessage =
|
this.alertMessage =
|
||||||
"Clear your cache and start over (after data backup). See console log for more info.";
|
"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";
|
const url = endorserApiServer + "/api/report/rateLimits";
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
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 token = await accessToken(identity);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -396,23 +409,41 @@ export default class AccountViewView extends Vue {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await this.axios.get(url, { headers });
|
const resp = await this.axios.get(url, { headers });
|
||||||
|
// axios throws an exception on a 400
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
this.limits = resp.data;
|
this.limits = resp.data;
|
||||||
} else {
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const serverError = error as AxiosError;
|
||||||
|
|
||||||
this.alertTitle = "Error from Server";
|
this.alertTitle = "Error from Server";
|
||||||
console.log("Bad response retrieving limits: ", resp.data);
|
console.log("Bad response retrieving limits: ", serverError);
|
||||||
if (resp.data.error?.message) {
|
// Anybody know how to access items inside "response.data" without this?
|
||||||
this.alertMessage = resp.data.error?.message;
|
// 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 {
|
} else {
|
||||||
this.alertMessage = "Bad server response of " + resp.status;
|
this.alertMessage = "Bad server response. See logs for details.";
|
||||||
}
|
}
|
||||||
this.isAlertVisible = true;
|
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() {
|
public showContactGivesClassNames() {
|
||||||
|
|||||||
@@ -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
|
showGiveTotals
|
||||||
? "Totals"
|
? "Total"
|
||||||
: showGiveConfirmed
|
: showGiveConfirmed
|
||||||
? "Confirmed"
|
? "Confirmed"
|
||||||
: "Unconfirmed"
|
: "Unconfirmed"
|
||||||
@@ -122,10 +122,10 @@
|
|||||||
@click="setVisibility(contact, false)"
|
@click="setVisibility(contact, false)"
|
||||||
>
|
>
|
||||||
<fa icon="eye" class="text-slate-900 fa-fw ml-1" />
|
<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>
|
||||||
<button v-else class="tooltip" @click="setVisibility(contact, true)">
|
<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" />
|
<fa icon="eye-slash" class="text-slate-900 fa-fw ml-1" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
<fa icon="person-circle-check" class="text-slate-900 fa-fw ml-1" />
|
<fa icon="person-circle-check" class="text-slate-900 fa-fw ml-1" />
|
||||||
</button>
|
</button>
|
||||||
<button v-else @click="register(contact)" class="tooltip">
|
<button v-else @click="register(contact)" class="tooltip">
|
||||||
<span class="tooltiptext">Maybe not registered</span>
|
<span class="tooltiptext">Registration Unknown</span>
|
||||||
<fa
|
<fa
|
||||||
icon="person-circle-question"
|
icon="person-circle-question"
|
||||||
class="text-slate-900 fa-fw ml-1"
|
class="text-slate-900 fa-fw ml-1"
|
||||||
@@ -165,18 +165,18 @@
|
|||||||
: (givenByMeUnconfirmed[contact.did] || 0)
|
: (givenByMeUnconfirmed[contact.did] || 0)
|
||||||
/* eslint-enable prettier/prettier */
|
/* eslint-enable prettier/prettier */
|
||||||
}}
|
}}
|
||||||
<span class="tooltiptext-left">{{
|
<span class="tooltiptext-left">
|
||||||
givenByMeDescriptions[contact.did]
|
{{ givenByMeDescriptions[contact.did] || "Nothing" }}
|
||||||
}}</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tooltip px-2">
|
<div class="tooltip px-2">
|
||||||
by:
|
from:
|
||||||
{{
|
{{
|
||||||
/* eslint-disable prettier/prettier */
|
/* eslint-disable prettier/prettier */
|
||||||
this.showGiveTotals
|
this.showGiveTotals
|
||||||
@@ -188,21 +188,32 @@
|
|||||||
/* eslint-enable prettier/prettier */
|
/* eslint-enable prettier/prettier */
|
||||||
}}
|
}}
|
||||||
<span class="tooltiptext-left">
|
<span class="tooltiptext-left">
|
||||||
{{ givenToMeDescriptions[contact.did] }}
|
{{ givenToMeDescriptions[contact.did] || "Nothing" }}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
|
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>
|
</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div v-bind:class="computedAlertClassNames()">
|
<div v-bind:class="computedAlertClassNames()">
|
||||||
<button
|
<button
|
||||||
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
|
class="close-button bg-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 { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import { AppString } from "@/constants/app";
|
import { AppString } from "@/constants/app";
|
||||||
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
|
||||||
import { accountsDB, db } from "@/db";
|
import { accountsDB, db } from "@/db";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||||
|
import {
|
||||||
|
GiveServerRecord,
|
||||||
|
GiveVerifiableCredential,
|
||||||
|
RegisterVerifiableCredential,
|
||||||
|
SERVICE_ID,
|
||||||
|
} from "@/libs/endorserServer";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const Buffer = require("buffer/").Buffer;
|
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({
|
@Options({
|
||||||
components: {},
|
components: {},
|
||||||
})
|
})
|
||||||
export default class ContactsView extends Vue {
|
export default class ContactsView extends Vue {
|
||||||
|
activeDid = "";
|
||||||
contacts: Array<Contact> = [];
|
contacts: Array<Contact> = [];
|
||||||
contactInput = "";
|
contactInput = "";
|
||||||
// { "did:...": concatenated-descriptions } entry for each contact
|
// { "did:...": concatenated-descriptions } entry for each contact
|
||||||
@@ -281,19 +269,16 @@ export default class ContactsView extends Vue {
|
|||||||
givenToMeUnconfirmed: Record<string, number> = {};
|
givenToMeUnconfirmed: Record<string, number> = {};
|
||||||
hourDescriptionInput = "";
|
hourDescriptionInput = "";
|
||||||
hourInput = "0";
|
hourInput = "0";
|
||||||
identity: IIdentifier | null = null;
|
|
||||||
showGiveNumbers = false;
|
showGiveNumbers = false;
|
||||||
showGiveTotals = true;
|
showGiveTotals = true;
|
||||||
showGiveConfirmed = true;
|
showGiveConfirmed = true;
|
||||||
|
|
||||||
// 'created' hook runs when the Vue instance is first created
|
// 'created' hook runs when the Vue instance is first created
|
||||||
async created() {
|
async created() {
|
||||||
await accountsDB.open();
|
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
|
||||||
this.identity = JSON.parse(accounts[0].identity);
|
|
||||||
|
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
|
this.activeDid = settings?.activeDid || "";
|
||||||
|
|
||||||
this.showGiveNumbers = !!settings?.showContactGivesInline;
|
this.showGiveNumbers = !!settings?.showContactGivesInline;
|
||||||
if (this.showGiveNumbers) {
|
if (this.showGiveNumbers) {
|
||||||
this.loadGives();
|
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> {
|
async onClickNewContact(): Promise<void> {
|
||||||
let did = this.contactInput;
|
let did = this.contactInput;
|
||||||
let name, publicKeyBase64;
|
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) {
|
async deleteContact(contact: Contact) {
|
||||||
if (
|
if (
|
||||||
confirm(
|
confirm(
|
||||||
@@ -454,7 +468,9 @@ export default class ContactsView extends Vue {
|
|||||||
) {
|
) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
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
|
// Make a claim
|
||||||
const vcClaim: RegisterVerifiableCredential = {
|
const vcClaim: RegisterVerifiableCredential = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
@@ -518,7 +534,7 @@ export default class ContactsView extends Vue {
|
|||||||
userMessage = error as string;
|
userMessage = error as string;
|
||||||
}
|
}
|
||||||
// Now set that error for the user to see.
|
// Now set that error for the user to see.
|
||||||
this.alertTitle = "Error with Server";
|
this.alertTitle = "Error With Server";
|
||||||
this.alertMessage = userMessage;
|
this.alertMessage = userMessage;
|
||||||
this.isAlertVisible = true;
|
this.isAlertVisible = true;
|
||||||
}
|
}
|
||||||
@@ -534,7 +550,9 @@ export default class ContactsView extends Vue {
|
|||||||
(visibility ? "canSeeMe" : "cannotSeeMe");
|
(visibility ? "canSeeMe" : "cannotSeeMe");
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
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 token = await accessToken(identity);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -548,7 +566,7 @@ export default class ContactsView extends Vue {
|
|||||||
contact.seesMe = visibility;
|
contact.seesMe = visibility;
|
||||||
db.contacts.update(contact.did, { seesMe: visibility });
|
db.contacts.update(contact.did, { seesMe: visibility });
|
||||||
} else {
|
} else {
|
||||||
this.alertTitle = "Error from Server";
|
this.alertTitle = "Error With Server";
|
||||||
console.log("Bad response setting visibility: ", resp.data);
|
console.log("Bad response setting visibility: ", resp.data);
|
||||||
if (resp.data.error?.message) {
|
if (resp.data.error?.message) {
|
||||||
this.alertMessage = resp.data.error?.message;
|
this.alertMessage = resp.data.error?.message;
|
||||||
@@ -558,7 +576,7 @@ export default class ContactsView extends Vue {
|
|||||||
this.isAlertVisible = true;
|
this.isAlertVisible = true;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.alertTitle = "Error from Server";
|
this.alertTitle = "Error With Server";
|
||||||
this.alertMessage = err as string;
|
this.alertMessage = err as string;
|
||||||
this.isAlertVisible = true;
|
this.isAlertVisible = true;
|
||||||
}
|
}
|
||||||
@@ -572,7 +590,9 @@ export default class ContactsView extends Vue {
|
|||||||
encodeURIComponent(contact.did);
|
encodeURIComponent(contact.did);
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
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 token = await accessToken(identity);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -594,7 +614,7 @@ export default class ContactsView extends Vue {
|
|||||||
"see your activity.";
|
"see your activity.";
|
||||||
this.isAlertVisible = true;
|
this.isAlertVisible = true;
|
||||||
} else {
|
} else {
|
||||||
this.alertTitle = "Error from Server";
|
this.alertTitle = "Error With Server";
|
||||||
if (resp.data.error?.message) {
|
if (resp.data.error?.message) {
|
||||||
this.alertMessage = resp.data.error?.message;
|
this.alertMessage = resp.data.error?.message;
|
||||||
} else {
|
} else {
|
||||||
@@ -603,7 +623,7 @@ export default class ContactsView extends Vue {
|
|||||||
this.isAlertVisible = true;
|
this.isAlertVisible = true;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.alertTitle = "Error from Server";
|
this.alertTitle = "Error With Server";
|
||||||
this.alertMessage = err as string;
|
this.alertMessage = err as string;
|
||||||
this.isAlertVisible = true;
|
this.isAlertVisible = true;
|
||||||
}
|
}
|
||||||
@@ -625,6 +645,27 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onClickAddGive(fromDid: string, toDid: string): Promise<void> {
|
async onClickAddGive(fromDid: string, toDid: string): Promise<void> {
|
||||||
|
await accountsDB.open();
|
||||||
|
const 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)) {
|
if (!this.isNumeric(this.hourInput)) {
|
||||||
this.alertTitle = "Input Error";
|
this.alertTitle = "Input Error";
|
||||||
this.alertMessage =
|
this.alertMessage =
|
||||||
@@ -634,13 +675,14 @@ export default class ContactsView extends Vue {
|
|||||||
this.alertTitle = "Input Error";
|
this.alertTitle = "Input Error";
|
||||||
this.alertMessage = "Giving 0 hours does nothing.";
|
this.alertMessage = "Giving 0 hours does nothing.";
|
||||||
this.isAlertVisible = true;
|
this.isAlertVisible = true;
|
||||||
} else if (!this.identity) {
|
} else if (!identity) {
|
||||||
this.alertTitle = "Status Error";
|
this.alertTitle = "Status Error";
|
||||||
this.alertMessage = "No identity is available.";
|
this.alertMessage = "No identity is available.";
|
||||||
this.isAlertVisible = true;
|
this.isAlertVisible = true;
|
||||||
} else {
|
} else {
|
||||||
|
// ask to confirm amount
|
||||||
let toFrom;
|
let toFrom;
|
||||||
if (fromDid == this.identity?.did) {
|
if (fromDid == identity?.did) {
|
||||||
toFrom = "from you to " + this.nameForDid(this.contacts, toDid);
|
toFrom = "from you to " + this.nameForDid(this.contacts, toDid);
|
||||||
} else {
|
} else {
|
||||||
toFrom = "from " + this.nameForDid(this.contacts, fromDid) + " to you";
|
toFrom = "from " + this.nameForDid(this.contacts, fromDid) + " to you";
|
||||||
@@ -662,7 +704,7 @@ export default class ContactsView extends Vue {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this.createAndSubmitGive(
|
this.createAndSubmitGive(
|
||||||
this.identity,
|
identity,
|
||||||
fromDid,
|
fromDid,
|
||||||
toDid,
|
toDid,
|
||||||
parseFloat(this.hourInput),
|
parseFloat(this.hourInput),
|
||||||
@@ -726,20 +768,17 @@ export default class ContactsView extends Vue {
|
|||||||
const resp = await this.axios.post(url, payload, { headers });
|
const resp = await this.axios.post(url, payload, { headers });
|
||||||
//console.log("Got resp data:", resp.data);
|
//console.log("Got resp data:", resp.data);
|
||||||
if (resp.data?.success?.handleId) {
|
if (resp.data?.success?.handleId) {
|
||||||
this.alertTitle = "";
|
this.alertTitle = "Done";
|
||||||
this.alertMessage = "";
|
this.alertMessage = "Successfully logged time to the server.";
|
||||||
|
this.isAlertVisible = true;
|
||||||
if (fromDid === identity.did) {
|
if (fromDid === identity.did) {
|
||||||
this.givenByMeConfirmed[toDid] =
|
const newList = R.clone(this.givenByMeUnconfirmed);
|
||||||
this.givenByMeConfirmed[toDid] + amount;
|
newList[toDid] = (newList[toDid] || 0) + amount;
|
||||||
// do this to update the UI (is there a better way?)
|
this.givenByMeUnconfirmed = newList;
|
||||||
// eslint-disable-next-line no-self-assign
|
|
||||||
this.givenByMeConfirmed = this.givenByMeConfirmed;
|
|
||||||
} else {
|
} else {
|
||||||
this.givenToMeConfirmed[fromDid] =
|
const newList = R.clone(this.givenToMeConfirmed);
|
||||||
this.givenToMeConfirmed[fromDid] + amount;
|
newList[fromDid] = (newList[fromDid] || 0) + amount;
|
||||||
// do this to update the UI (is there a better way?)
|
this.givenToMeConfirmed = newList;
|
||||||
// eslint-disable-next-line no-self-assign
|
|
||||||
this.givenToMeConfirmed = this.givenToMeConfirmed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -755,26 +794,13 @@ export default class ContactsView extends Vue {
|
|||||||
userMessage = error as string;
|
userMessage = error as string;
|
||||||
}
|
}
|
||||||
// Now set that error for the user to see.
|
// Now set that error for the user to see.
|
||||||
this.alertTitle = "Error with Server";
|
this.alertTitle = "Error With Server";
|
||||||
this.alertMessage = userMessage;
|
this.alertMessage = userMessage;
|
||||||
this.isAlertVisible = true;
|
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() {
|
public toggleShowGiveTotals() {
|
||||||
if (this.showGiveTotals) {
|
if (this.showGiveTotals) {
|
||||||
this.showGiveTotals = false;
|
this.showGiveTotals = false;
|
||||||
@@ -817,7 +843,7 @@ export default class ContactsView extends Vue {
|
|||||||
|
|
||||||
public showGiveAmountsClassNames() {
|
public showGiveAmountsClassNames() {
|
||||||
return {
|
return {
|
||||||
"bg-slate-900": this.showGiveTotals,
|
"bg-slate-500": this.showGiveTotals,
|
||||||
"bg-green-600": !this.showGiveTotals && this.showGiveConfirmed,
|
"bg-green-600": !this.showGiveTotals && this.showGiveConfirmed,
|
||||||
"bg-yellow-600": !this.showGiveTotals && !this.showGiveConfirmed,
|
"bg-yellow-600": !this.showGiveTotals && !this.showGiveConfirmed,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,7 +45,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
||||||
import { accountsDB } from "@/db";
|
import { accountsDB, db } from "@/db";
|
||||||
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
|
||||||
@Options({
|
@Options({
|
||||||
components: {},
|
components: {},
|
||||||
@@ -76,16 +77,20 @@ export default class ImportAccountView extends Vue {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const num_accounts = await accountsDB.accounts.count();
|
|
||||||
if (num_accounts === 0) {
|
|
||||||
await accountsDB.accounts.add({
|
await accountsDB.accounts.add({
|
||||||
dateCreated: new Date().toISOString(),
|
dateCreated: new Date().toISOString(),
|
||||||
derivationPath: this.derivationPath,
|
derivationPath: this.derivationPath,
|
||||||
|
did: newId.did,
|
||||||
identity: JSON.stringify(newId),
|
identity: JSON.stringify(newId),
|
||||||
mnemonic: mne,
|
mnemonic: mne,
|
||||||
publicKeyHex: newId.keys[0].publicKeyHex,
|
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" });
|
this.$router.push({ name: "account" });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Error!");
|
console.log("Error!");
|
||||||
|
|||||||
@@ -70,10 +70,8 @@ export default class NewEditAccountView extends Vue {
|
|||||||
async created() {
|
async created() {
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
if (settings) {
|
this.firstName = settings?.firstName || "";
|
||||||
this.firstName = settings.firstName || "";
|
this.lastName = settings?.lastName || "";
|
||||||
this.lastName = settings.lastName || "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickSaveChanges() {
|
onClickSaveChanges() {
|
||||||
|
|||||||
@@ -76,14 +76,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { 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 {
|
interface VerifiableCredential {
|
||||||
"@context": string;
|
"@context": string;
|
||||||
@@ -97,6 +100,7 @@ interface VerifiableCredential {
|
|||||||
components: {},
|
components: {},
|
||||||
})
|
})
|
||||||
export default class NewEditProjectView extends Vue {
|
export default class NewEditProjectView extends Vue {
|
||||||
|
activeDid = "";
|
||||||
projectName = "";
|
projectName = "";
|
||||||
description = "";
|
description = "";
|
||||||
errorMessage = "";
|
errorMessage = "";
|
||||||
@@ -109,16 +113,19 @@ export default class NewEditProjectView extends Vue {
|
|||||||
|
|
||||||
// 'created' hook runs when the Vue instance is first created
|
// 'created' hook runs when the Vue instance is first created
|
||||||
async created() {
|
async created() {
|
||||||
if (this.projectId === "") {
|
await db.open();
|
||||||
console.log("This is a new project");
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
} else {
|
this.activeDid = settings?.activeDid || "";
|
||||||
|
|
||||||
|
if (this.projectId) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const num_accounts = await accountsDB.accounts.count();
|
const num_accounts = await accountsDB.accounts.count();
|
||||||
if (num_accounts === 0) {
|
if (num_accounts === 0) {
|
||||||
console.log("Problem! Should have a profile!");
|
console.log("Problem! Should have a profile!");
|
||||||
} else {
|
} else {
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
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);
|
this.LoadProject(identity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +264,8 @@ export default class NewEditProjectView extends Vue {
|
|||||||
console.log("Problem! Should have a profile!");
|
console.log("Problem! Should have a profile!");
|
||||||
} else {
|
} else {
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
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);
|
this.SaveProject(identity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,13 +146,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { 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({
|
@Options({
|
||||||
components: {},
|
components: {},
|
||||||
@@ -226,13 +229,18 @@ export default class ProjectViewView extends Vue {
|
|||||||
|
|
||||||
// 'created' hook runs when the Vue instance is first created
|
// 'created' hook runs when the Vue instance is first created
|
||||||
async created() {
|
async created() {
|
||||||
|
await db.open();
|
||||||
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
|
const activeDid = settings?.activeDid || "";
|
||||||
|
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const num_accounts = await accountsDB.accounts.count();
|
const num_accounts = await accountsDB.accounts.count();
|
||||||
if (num_accounts === 0) {
|
if (num_accounts === 0) {
|
||||||
console.log("Problem! Should have a profile!");
|
console.log("Problem! Should have a profile!");
|
||||||
} else {
|
} else {
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
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);
|
this.LoadProject(identity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,11 +101,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import * as R from "ramda";
|
||||||
import { Options, Vue } from "vue-class-component";
|
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 { 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({
|
@Options({
|
||||||
components: {},
|
components: {},
|
||||||
@@ -155,13 +158,18 @@ export default class ProjectsView extends Vue {
|
|||||||
|
|
||||||
// 'created' hook runs when the Vue instance is first created
|
// 'created' hook runs when the Vue instance is first created
|
||||||
async created() {
|
async created() {
|
||||||
|
await db.open();
|
||||||
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
|
const activeDid = settings?.activeDid || "";
|
||||||
|
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const num_accounts = await accountsDB.accounts.count();
|
const num_accounts = await accountsDB.accounts.count();
|
||||||
if (num_accounts === 0) {
|
if (num_accounts === 0) {
|
||||||
console.log("Problem! Should have a profile!");
|
console.log("Problem! Should have a profile!");
|
||||||
} else {
|
} else {
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
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);
|
this.LoadProjects(identity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user