forked from trent_larson/crowd-funder-for-time-pwa
Compare commits
23 Commits
quicknav-c
...
project-gi
| Author | SHA1 | Date | |
|---|---|---|---|
| 69ae5fe2ff | |||
| 0b7a35c9b8 | |||
| 0257901c5b | |||
| d9d6096275 | |||
| 81dd6eb595 | |||
|
|
c61bb88788 | ||
|
|
3bd55f3ad2 | ||
|
|
3471afdf25 | ||
|
|
e25a83ff1b | ||
|
|
0fbdb45d3e | ||
|
|
dc23ba1375 | ||
|
|
08137eb000 | ||
|
|
5d49965166 | ||
| 8e8aa4356d | |||
| 59a354027e | |||
|
|
5dc80ce12a | ||
|
|
754bced2a9 | ||
|
|
e3f58bd593 | ||
|
|
3b41014083 | ||
| f568149745 | |||
| a27d035e9b | |||
| 16d0be681c | |||
| e42b3ff11d |
950
package-lock.json
generated
950
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -21,15 +21,15 @@
|
||||
"@veramo/did-provider-ethr": "^5.1.2",
|
||||
"@veramo/did-resolver": "^5.2.0",
|
||||
"@veramo/key-manager": "^5.1.2",
|
||||
"@vueuse/core": "^10.2.0",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"@zxing/text-encoding": "^0.9.0",
|
||||
"axios": "^1.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"core-js": "^3.31.0",
|
||||
"core-js": "^3.31.1",
|
||||
"dexie": "^3.2.4",
|
||||
"dexie-export-import": "^4.0.7",
|
||||
"did-jwt": "^7.2.2",
|
||||
"did-jwt": "^7.2.4",
|
||||
"ethereum-cryptography": "^2.0.0",
|
||||
"ethereumjs-util": "^7.1.5",
|
||||
"ethr-did-resolver": "^8.0.0",
|
||||
@@ -43,23 +43,22 @@
|
||||
"pinia-plugin-persistedstate": "^3.1.0",
|
||||
"qr-code-generator-vue3": "^1.4.21",
|
||||
"ramda": "^0.29.0",
|
||||
"readable-stream": "^4.4.0",
|
||||
"readable-stream": "^4.4.2",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"three": "^0.153.0",
|
||||
"three": "^0.154.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-axios": "^3.5.2",
|
||||
"vue-class-component": "^8.0.0-0",
|
||||
"vue-facing-decorator": "^2.1.20",
|
||||
"vue-property-decorator": "^9.1.2",
|
||||
"vue-router": "^4.2.2",
|
||||
"web-did-resolver": "^2.0.24"
|
||||
"vue-router": "^4.2.3",
|
||||
"web-did-resolver": "^2.0.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ramda": "^0.29.2",
|
||||
"@types/ramda": "^0.29.3",
|
||||
"@types/three": "^0.152.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
||||
"@typescript-eslint/parser": "^5.60.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
||||
"@typescript-eslint/parser": "^5.61.0",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||
"@vue/cli-plugin-pwa": "~5.0.8",
|
||||
@@ -69,13 +68,13 @@
|
||||
"@vue/cli-service": "~5.0.8",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.43.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.15.0",
|
||||
"eslint-plugin-prettier": "^5.0.0-alpha.1",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"postcss": "^8.4.24",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier": "^3.0.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "~5.1.3"
|
||||
"typescript": "~5.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
|
||||
tasks:
|
||||
- replace user-affecting console.log & console.error with error messages (eg. catches)
|
||||
- if there's no identity, handle it on pages which expect an identity (eg. project -- look for JSON.parse identity calls)
|
||||
- .1 show an appropriate message when there are no contacts
|
||||
|
||||
- 8 Move to vue-facing-decorator
|
||||
|
||||
- 01 design ideas for simple gives on the first page
|
||||
|
||||
- .1 remove commitments from ProjectView UI
|
||||
- .5 audit all console.log calls
|
||||
- 01 design ideas for simple gives on the Home page
|
||||
- 01 add list of 'give' records for a project on ProjectView UI
|
||||
|
||||
- 02 Discover page - display results (currently in console.log), spin when searching
|
||||
- 02 Discover page - display results (currently in console.log) & link, spin when searching
|
||||
- 08 search by location, endpoint, etc assignee:trent
|
||||
- 01 add a location for a project via map pin :
|
||||
- give attribute to use assignee:trent
|
||||
- 01 add a location for a project via map pin (see API by clicking on "Nearby")
|
||||
- 01 remove all the "form" fields (or at least investigate to see if that page refresh is desired)
|
||||
- .2 change "errorMessage" to "alertMessage" on ProjectViewView.vue
|
||||
|
||||
- 08 Scan QR code to import into contacts.
|
||||
|
||||
@@ -30,8 +22,6 @@ tasks:
|
||||
|
||||
- refactor UI :
|
||||
- .5 Alerts show at the top and can be missed, eg. account data download
|
||||
- 01 Change alert popup code a component (to cut down duplicate code; see "in many files")
|
||||
- 01 Change "nav" tabs across the bottom into a component (eliminating duplicate code).
|
||||
- .5 Fix how icons show on top of bottom bar on ContactAmounts page
|
||||
- .2 Hide "Advanced" section in Account page by default
|
||||
|
||||
@@ -44,7 +34,6 @@ tasks:
|
||||
- 01 quick action - send action, maybe choose via canvas tool https://github.com/konvajs/vue-konva
|
||||
|
||||
- .5 customize favicon
|
||||
- .2 Hide "Advanced" section in Account page by default
|
||||
- 04 allow user to download claims, mine + ones I can see about me from others
|
||||
|
||||
- 24 Move to Vite
|
||||
@@ -62,10 +51,9 @@ tasks:
|
||||
- 01 link to world for specific stats
|
||||
- .5 don't load another instance of a bush if it already exists
|
||||
- maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version")
|
||||
- 4-8 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
|
||||
- 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
|
||||
|
||||
- Do we want split first name & last name?
|
||||
- remove 'about' page
|
||||
|
||||
- Release Minimum Viable Product :
|
||||
- 08 thorough testing for errors & edge cases
|
||||
@@ -82,9 +70,10 @@ tasks:
|
||||
- pull, w/ scheduled runs
|
||||
|
||||
- linking between projects or plans :
|
||||
- terminology:
|
||||
- for subtasks: fulfills (is it really the same?), feeds, contributes to, supplies, boosts, advances
|
||||
- for blocking: blocks, precedes, comes before, is sought by -- vs follows, seeks, builds on ("contributes to" isn't specific enough, "succeeds" has different, possibly confusing meaning)
|
||||
- show total time given to & from a project
|
||||
- terminology:
|
||||
- for subtasks: fulfills (is it really the same?), feeds, contributes to, supplies, boosts, advances
|
||||
- for blocking: blocks, precedes, comes before, is sought by -- vs follows, seeks, builds on ("contributes to" isn't specific enough, "succeeds" has different, possibly confusing meaning)
|
||||
|
||||
- Stats :
|
||||
- 01 point out user's location on the world
|
||||
@@ -104,7 +93,6 @@ tasks:
|
||||
|
||||
- Peer DID
|
||||
|
||||
|
||||
- DIDComm
|
||||
|
||||
- Write to or read from a different ledger (eg. private ACDC, attest.sh)
|
||||
|
||||
@@ -119,15 +119,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||
|
||||
@Options({
|
||||
props: {
|
||||
msg: String,
|
||||
},
|
||||
})
|
||||
@Component
|
||||
export default class HelloWorld extends Vue {
|
||||
msg!: string;
|
||||
@Prop msg!: string;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class InfiniteScroll extends Vue {
|
||||
};
|
||||
this.observer = new IntersectionObserver(
|
||||
this.handleIntersection,
|
||||
options
|
||||
options,
|
||||
);
|
||||
this.observer.observe(this.$refs.sentinel as HTMLElement);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ function createCamera() {
|
||||
35, // fov = Field Of View
|
||||
1, // aspect ratio (dummy value)
|
||||
0.1, // near clipping plane
|
||||
350 // far clipping plane
|
||||
350, // far clipping plane
|
||||
);
|
||||
|
||||
// move the camera back so we can view the scene
|
||||
|
||||
@@ -22,17 +22,16 @@ export async function loadLandmarks(vue, world, scene, loop) {
|
||||
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");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
const token = await accessToken(identity);
|
||||
|
||||
const url = apiServer + "/api/v2/report/claims?claimType=GiveAction";
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (identity) {
|
||||
const token = await accessToken(identity);
|
||||
headers["Authorization"] = "Bearer " + token;
|
||||
}
|
||||
|
||||
const url = apiServer + "/api/v2/report/claims?claimType=GiveAction";
|
||||
const resp = await axios.get(url, { headers: headers });
|
||||
if (resp.status === 200) {
|
||||
const landmarks = resp.data.data;
|
||||
@@ -63,7 +62,11 @@ export async function loadLandmarks(vue, world, scene, loop) {
|
||||
|
||||
// calculate positions for each claim, especially because some are random
|
||||
const locations = landmarks.map((claim) =>
|
||||
locForGive(claim, world.PLATFORM_SIZE, world.PLATFORM_EDGE_FOR_UNKNOWNS)
|
||||
locForGive(
|
||||
claim,
|
||||
world.PLATFORM_SIZE,
|
||||
world.PLATFORM_EDGE_FOR_UNKNOWNS,
|
||||
),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
@@ -93,7 +96,7 @@ export async function loadLandmarks(vue, world, scene, loop) {
|
||||
undefined,
|
||||
function (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// calculate when lights shine on appearing claim area
|
||||
@@ -121,7 +124,7 @@ export async function loadLandmarks(vue, world, scene, loop) {
|
||||
.onComplete(() => {
|
||||
scene.remove(light);
|
||||
light.dispose();
|
||||
})
|
||||
}),
|
||||
)
|
||||
.start();
|
||||
world.lights = [...world.lights, light];
|
||||
@@ -130,18 +133,18 @@ export async function loadLandmarks(vue, world, scene, loop) {
|
||||
console.error(
|
||||
"Got bad server response status & data of",
|
||||
resp.status,
|
||||
resp.data
|
||||
resp.data,
|
||||
);
|
||||
vue.setAlert(
|
||||
"Error With Server",
|
||||
"There was an error retrieving your claims from the server."
|
||||
"There was an error retrieving your claims from the server.",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Got exception contacting server:", error);
|
||||
vue.setAlert(
|
||||
"Error With Server",
|
||||
"There was a problem retrieving your claims from the server."
|
||||
"There was a problem retrieving your claims from the server.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const newIdentifier = (
|
||||
address: string,
|
||||
publicHex: string,
|
||||
privateHex: string,
|
||||
derivationPath: string
|
||||
derivationPath: string,
|
||||
): Omit<IIdentifier, keyof "provider"> => {
|
||||
return {
|
||||
did: DEFAULT_DID_PROVIDER_NAME + ":" + address,
|
||||
@@ -46,7 +46,7 @@ export const newIdentifier = (
|
||||
* @return {*} {[string, string, string, string]}
|
||||
*/
|
||||
export const deriveAddress = (
|
||||
mnemonic: string
|
||||
mnemonic: string,
|
||||
): [string, string, string, string] => {
|
||||
const UPORT_ROOT_DERIVATION_PATH = "m/7696500'/0'/0'/0'";
|
||||
mnemonic = mnemonic.trim().toLowerCase();
|
||||
@@ -134,7 +134,7 @@ export function fromJose(signature: string): {
|
||||
const signatureBytes: Uint8Array = didJwt.base64ToBytes(signature);
|
||||
if (signatureBytes.length < 64 || signatureBytes.length > 65) {
|
||||
throw new TypeError(
|
||||
`Wrong size for signature. Expected 64 or 65 bytes, but got ${signatureBytes.length}`
|
||||
`Wrong size for signature. Expected 64 or 65 bytes, but got ${signatureBytes.length}`,
|
||||
);
|
||||
}
|
||||
const r = bytesToHex(signatureBytes.slice(0, 32));
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IIdentifier } from "@veramo/core";
|
||||
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||
import * as didJwt from "did-jwt";
|
||||
import { Axios, AxiosResponse } from "axios";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
|
||||
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
||||
export const SERVICE_ID = "endorser.ch";
|
||||
@@ -81,12 +82,15 @@ export function isHiddenDid(did) {
|
||||
/**
|
||||
always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY
|
||||
**/
|
||||
export function didInfo(did, identifiers, contacts) {
|
||||
const myId = R.find((i) => i.did === did, identifiers);
|
||||
export function didInfo(did, activeDid, identifiers, contacts) {
|
||||
const myId: IIdentifier | undefined = R.find(
|
||||
(i) => i.did === did,
|
||||
identifiers,
|
||||
);
|
||||
if (myId) {
|
||||
return "You";
|
||||
return "You" + (myId.did !== activeDid ? " (Alt ID)" : "");
|
||||
} else {
|
||||
const contact = R.find((c) => c.did === did, contacts);
|
||||
const contact: Contact | undefined = R.find((c) => c.did === did, contacts);
|
||||
if (contact) {
|
||||
return contact.name || "Someone Unnamed in Contacts";
|
||||
} else if (!did) {
|
||||
@@ -116,7 +120,7 @@ export async function createAndSubmitGive(
|
||||
toDid: string,
|
||||
description: string,
|
||||
hours: number,
|
||||
fulfillsProjectHandleId?: string
|
||||
fulfillsProjectHandleId?: string,
|
||||
): Promise<AxiosResponse<ClaimResult> | InternalError> {
|
||||
// Make a claim
|
||||
const vcClaim: GiveVerifiableCredential = {
|
||||
|
||||
@@ -81,7 +81,7 @@ function didProviderName(netName: string) {
|
||||
const DEFAULT_DID_PROVIDER_NETWORK_NAME = "mainnet";
|
||||
|
||||
export const DEFAULT_DID_PROVIDER_NAME = didProviderName(
|
||||
DEFAULT_DID_PROVIDER_NETWORK_NAME
|
||||
DEFAULT_DID_PROVIDER_NETWORK_NAME,
|
||||
);
|
||||
|
||||
export const HANDY_APP = false;
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
faCircleUser,
|
||||
faClock,
|
||||
faCoins,
|
||||
faComment,
|
||||
faCopy,
|
||||
faEllipsisVertical,
|
||||
faEye,
|
||||
@@ -58,6 +59,7 @@ library.add(
|
||||
faCircleUser,
|
||||
faClock,
|
||||
faCoins,
|
||||
faComment,
|
||||
faCopy,
|
||||
faEllipsisVertical,
|
||||
faEye,
|
||||
@@ -84,7 +86,7 @@ library.add(
|
||||
faTrashCan,
|
||||
faUser,
|
||||
faUsers,
|
||||
faXmark
|
||||
faXmark,
|
||||
);
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
|
||||
@@ -7,7 +7,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
ready() {
|
||||
console.log(
|
||||
"App is being served from cache by a service worker.\n" +
|
||||
"For more details, visit https://goo.gl/AFskqB"
|
||||
"For more details, visit https://goo.gl/AFskqB",
|
||||
);
|
||||
},
|
||||
registered() {
|
||||
@@ -24,7 +24,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
},
|
||||
offline() {
|
||||
console.log(
|
||||
"No internet connection found. App is running in offline mode."
|
||||
"No internet connection found. App is running in offline mode.",
|
||||
);
|
||||
},
|
||||
error(error) {
|
||||
|
||||
@@ -25,12 +25,6 @@ const routes: Array<RouteRecordRaw> = [
|
||||
import(/* webpackChunkName: "home" */ "../views/HomeView.vue"),
|
||||
beforeEnter: enterOrStart,
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
name: "about",
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/account",
|
||||
name: "account",
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
|
||||
@Options({
|
||||
components: {},
|
||||
})
|
||||
export default class AboutView extends Vue {}
|
||||
</script>
|
||||
@@ -243,6 +243,11 @@
|
||||
|
||||
<div v-if="numAccounts > 0" class="flex py-2">
|
||||
Switch Identifier
|
||||
<span>
|
||||
<button class="text-blue-500 px-2" @click="switchAccount(0)">
|
||||
None
|
||||
</button>
|
||||
</span>
|
||||
<span v-for="accountNum in numAccounts" :key="accountNum">
|
||||
<button class="text-blue-500 px-2" @click="switchAccount(accountNum)">
|
||||
#{{ accountNum }}
|
||||
@@ -269,12 +274,12 @@
|
||||
|
||||
<script lang="ts">
|
||||
import "dexie-export-import";
|
||||
import * as R from "ramda";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
|
||||
import { AppString } from "@/constants/app";
|
||||
import { db, accountsDB } from "@/db";
|
||||
import { AccountsSchema } from "@/db/tables/accounts";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import { AxiosError } from "axios/index";
|
||||
@@ -310,12 +315,38 @@ export default class AccountViewView extends Vue {
|
||||
limitsMessage = "";
|
||||
loadingLimits = true; // might as well now that we do it on mount, to avoid flashing the registration message
|
||||
showContactGives = false;
|
||||
private accounts: AccountsSchema;
|
||||
|
||||
showDidCopy = false;
|
||||
showDerCopy = false;
|
||||
showB64Copy = false;
|
||||
showPubCopy = false;
|
||||
|
||||
public async getIdentity(activeDid) {
|
||||
await accountsDB.open();
|
||||
const account = await accountsDB.accounts
|
||||
.where("did")
|
||||
.equals(activeDid)
|
||||
.first();
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
throw new Error(
|
||||
"Attempted to load Give records with no identity available.",
|
||||
);
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
public async getHeaders(identity) {
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
// call fn, copy text to the clipboard, then redo fn after 2 seconds
|
||||
doCopyTwoSecRedo(text, fn) {
|
||||
fn();
|
||||
@@ -333,7 +364,12 @@ export default class AccountViewView extends Vue {
|
||||
return timeStr.substring(0, timeStr.indexOf("T"));
|
||||
}
|
||||
|
||||
// 'created' hook runs when the Vue instance is first created
|
||||
async beforeCreate() {
|
||||
accountsDB.open();
|
||||
this.accounts = accountsDB.accounts;
|
||||
this.numAccounts = await this.accounts.count();
|
||||
}
|
||||
|
||||
async created() {
|
||||
// Uncomment this to register this user on the test server.
|
||||
// To manage within the vue devtools browser extension https://devtools.vuejs.org/
|
||||
@@ -351,14 +387,8 @@ export default class AccountViewView extends Vue {
|
||||
this.lastName = settings?.lastName || "";
|
||||
this.showContactGives = !!settings?.showContactGivesInline;
|
||||
|
||||
await accountsDB.open();
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
this.numAccounts = accounts.length;
|
||||
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
|
||||
this.publicHex = identity.keys[0].publicKeyHex;
|
||||
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
|
||||
this.derivationPath = identity.keys[0].meta.derivationPath;
|
||||
@@ -368,10 +398,21 @@ export default class AccountViewView extends Vue {
|
||||
});
|
||||
this.checkLimits();
|
||||
} catch (err) {
|
||||
this.alertMessage =
|
||||
"Clear your cache and start over (after data backup).";
|
||||
console.error("Telling user to clear cache at page create because:", err);
|
||||
this.alertTitle = "Error Creating Account";
|
||||
if (
|
||||
err.message ===
|
||||
"Attempted to load account records with no identity available."
|
||||
) {
|
||||
this.limitsMessage = "No identity.";
|
||||
this.loadingLimits = false;
|
||||
} else {
|
||||
this.alertMessage =
|
||||
"Clear your cache and start over (after data backup).";
|
||||
console.error(
|
||||
"Telling user to clear cache at page create because:",
|
||||
err,
|
||||
);
|
||||
this.alertTitle = "Error Creating Account";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +427,7 @@ export default class AccountViewView extends Vue {
|
||||
"Clear your cache and start over (after data backup).";
|
||||
console.error(
|
||||
"Telling user to clear cache after contact setting update because:",
|
||||
err
|
||||
err,
|
||||
);
|
||||
this.alertTitle = "Error Updating Contact Setting";
|
||||
}
|
||||
@@ -417,53 +458,62 @@ export default class AccountViewView extends Vue {
|
||||
this.loadingLimits = true;
|
||||
this.limitsMessage = "";
|
||||
|
||||
const url = this.apiServer + "/api/report/rateLimits";
|
||||
await accountsDB.open();
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
|
||||
try {
|
||||
const url = this.apiServer + "/api/report/rateLimits";
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
const headers = await this.getHeaders(identity);
|
||||
|
||||
const resp = await this.axios.get(url, { headers });
|
||||
// axios throws an exception on a 400
|
||||
if (resp.status === 200) {
|
||||
this.limits = resp.data;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const serverError = error as AxiosError;
|
||||
|
||||
console.error("Bad response retrieving limits: ", serverError);
|
||||
// Anybody know how to access items inside "response.data" without this?
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data: any = serverError.response?.data;
|
||||
this.limitsMessage = data?.error?.message || "Bad server response.";
|
||||
if (
|
||||
error.message ===
|
||||
"Attempted to load Give records with no identity available."
|
||||
) {
|
||||
this.limitsMessage = "No identity.";
|
||||
this.loadingLimits = false;
|
||||
} else {
|
||||
const serverError = error as AxiosError;
|
||||
console.error("Bad response retrieving limits: ", serverError);
|
||||
// Anybody know how to access items inside "response.data" without this?
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data: any = serverError.response?.data;
|
||||
this.limitsMessage = data?.error?.message || "Bad server response.";
|
||||
}
|
||||
}
|
||||
|
||||
this.loadingLimits = false;
|
||||
}
|
||||
|
||||
async switchAccount(accountNum: number) {
|
||||
await accountsDB.open();
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = accounts[accountNum - 1];
|
||||
// 0 means none
|
||||
if (accountNum === 0) {
|
||||
await db.open();
|
||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
activeDid: undefined,
|
||||
});
|
||||
this.activeDid = "";
|
||||
this.derivationPath = "";
|
||||
this.publicHex = "";
|
||||
this.publicBase64 = "";
|
||||
} else {
|
||||
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,
|
||||
});
|
||||
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");
|
||||
this.activeDid = account.did;
|
||||
this.derivationPath = account.derivationPath;
|
||||
this.publicHex = account.publicKeyHex;
|
||||
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
|
||||
}
|
||||
}
|
||||
|
||||
public showContactGivesClassNames() {
|
||||
|
||||
@@ -48,9 +48,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
@Options({
|
||||
@Component({
|
||||
components: {},
|
||||
})
|
||||
export default class ConfirmContactView extends Vue {}
|
||||
|
||||
@@ -121,6 +121,7 @@ import * as R from "ramda";
|
||||
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { accountsDB, db } from "@/db";
|
||||
import { AccountsSchema } from "@/db/tables/accounts";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||
@@ -142,8 +143,40 @@ export default class ContactsView extends Vue {
|
||||
giveRecords: Array<GiveServerRecord> = [];
|
||||
alertTitle = "";
|
||||
alertMessage = "";
|
||||
accounts: AccountsSchema;
|
||||
numAccounts = 0;
|
||||
|
||||
async beforeCreate() {
|
||||
accountsDB.open();
|
||||
this.accounts = accountsDB.accounts;
|
||||
this.numAccounts = await this.accounts.count();
|
||||
}
|
||||
|
||||
public async getIdentity(activeDid) {
|
||||
await accountsDB.open();
|
||||
const account = await accountsDB.accounts
|
||||
.where("did")
|
||||
.equals(activeDid)
|
||||
.first();
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
throw new Error(
|
||||
"Attempted to load Give records with no identity available.",
|
||||
);
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
public async getHeaders(identity) {
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
// 'created' hook runs when the Vue instance is first created
|
||||
async created() {
|
||||
try {
|
||||
await db.open();
|
||||
@@ -166,19 +199,9 @@ export default class ContactsView extends Vue {
|
||||
}
|
||||
|
||||
async loadGives(activeDid: string, contact: Contact) {
|
||||
// only load the private keys temporarily when needed
|
||||
await accountsDB.open();
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
|
||||
// load all the time I have given to them
|
||||
try {
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
let result = [];
|
||||
|
||||
const url =
|
||||
this.apiServer +
|
||||
"/api/v2/report/gives?agentDid=" +
|
||||
@@ -197,7 +220,7 @@ export default class ContactsView extends Vue {
|
||||
console.error(
|
||||
"Got bad response status & data of",
|
||||
resp.status,
|
||||
resp.data
|
||||
resp.data,
|
||||
);
|
||||
this.alertTitle = "Error With Server";
|
||||
this.alertMessage =
|
||||
@@ -222,7 +245,7 @@ export default class ContactsView extends Vue {
|
||||
console.error(
|
||||
"Got bad response status & data of",
|
||||
resp2.status,
|
||||
resp2.data
|
||||
resp2.data,
|
||||
);
|
||||
this.alertTitle = "Error With Server";
|
||||
this.alertMessage =
|
||||
@@ -232,7 +255,7 @@ export default class ContactsView extends Vue {
|
||||
const sortedResult: Array<GiveServerRecord> = R.sort(
|
||||
(a, b) =>
|
||||
new Date(b.issuedAt).getTime() - new Date(a.issuedAt).getTime(),
|
||||
result
|
||||
result,
|
||||
);
|
||||
this.giveRecords = sortedResult;
|
||||
} catch (error) {
|
||||
@@ -266,13 +289,7 @@ export default class ContactsView extends Vue {
|
||||
};
|
||||
|
||||
// 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 || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
if (identity.keys[0].privateKeyHex !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const privateKeyHex: string = identity.keys[0].privateKeyHex!;
|
||||
|
||||
@@ -50,6 +50,29 @@ export default class ContactQRScanShow extends Vue {
|
||||
apiServer = "";
|
||||
qrValue = "";
|
||||
|
||||
public async getIdentity(activeDid) {
|
||||
await accountsDB.open();
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
throw new Error(
|
||||
"Attempted to load Give records with no identity available.",
|
||||
);
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
public async getHeaders(identity) {
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
async created() {
|
||||
await db.open();
|
||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||
@@ -62,11 +85,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
if (!account) {
|
||||
this.alertMessage = "You have no identity yet.";
|
||||
} else {
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
const publicKeyHex = identity.keys[0].publicKeyHex;
|
||||
const publicEncKey = Buffer.from(publicKeyHex, "hex").toString("base64");
|
||||
const contactInfo = {
|
||||
|
||||
@@ -87,9 +87,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
@Options({
|
||||
@Component({
|
||||
components: {},
|
||||
})
|
||||
export default class ContactScanView extends Vue {}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Results List -->
|
||||
<ul class="">
|
||||
<ul v-if="contacts.length > 0">
|
||||
<li
|
||||
class="border-b border-slate-300"
|
||||
v-for="contact in contacts"
|
||||
@@ -187,6 +187,7 @@
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else>This identity has no contacts.</p>
|
||||
<AlertMessage
|
||||
:alertTitle="alertTitle"
|
||||
:alertMessage="alertMessage"
|
||||
@@ -199,18 +200,16 @@ import { AxiosError } from "axios";
|
||||
import * as didJwt from "did-jwt";
|
||||
import * as R from "ramda";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
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";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import AlertMessage from "@/components/AlertMessage";
|
||||
import QuickNav from "@/components/QuickNav";
|
||||
|
||||
@@ -242,8 +241,9 @@ export default class ContactsView extends Vue {
|
||||
showGiveNumbers = false;
|
||||
showGiveTotals = true;
|
||||
showGiveConfirmed = true;
|
||||
alertTitle = "";
|
||||
alertMessage = "";
|
||||
|
||||
// 'created' hook runs when the Vue instance is first created
|
||||
async created() {
|
||||
await db.open();
|
||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||
@@ -257,123 +257,116 @@ export default class ContactsView extends Vue {
|
||||
const allContacts = await db.contacts.toArray();
|
||||
this.contacts = R.sort(
|
||||
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
||||
allContacts
|
||||
allContacts,
|
||||
);
|
||||
}
|
||||
|
||||
async loadGives() {
|
||||
public async getIdentity(activeDid) {
|
||||
await accountsDB.open();
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||
const account = R.find((acc) => acc.did === activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
console.error(
|
||||
"Attempted to load Give records with no identity available."
|
||||
throw new Error(
|
||||
"Attempted to load Give records with no identity available.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
public async getHeaders(identity) {
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
public async getHeadersAndIdentity(activeDid) {
|
||||
const identity = await this.getIdentity(activeDid);
|
||||
const headers = await this.getHeaders(identity);
|
||||
|
||||
return { headers, identity };
|
||||
}
|
||||
|
||||
async loadGives() {
|
||||
const handleResponse = (resp, descriptions, confirmed, unconfirmed) => {
|
||||
if (resp.status === 200) {
|
||||
const allData = resp.data.data;
|
||||
for (const give of allData) {
|
||||
if (give.unit === "HUR") {
|
||||
if (give.amountConfirmed) {
|
||||
const prevAmount = confirmed[give.agentDid] || 0;
|
||||
confirmed[give.agentDid] = prevAmount + give.amount;
|
||||
} else {
|
||||
const prevAmount = unconfirmed[give.agentDid] || 0;
|
||||
unconfirmed[give.agentDid] = prevAmount + give.amount;
|
||||
}
|
||||
if (!descriptions[give.agentDid] && give.description) {
|
||||
descriptions[give.agentDid] = give.description;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
"Got bad response status & data of",
|
||||
resp.status,
|
||||
resp.data,
|
||||
);
|
||||
this.alertTitle = "Error With Server";
|
||||
this.alertMessage =
|
||||
"Got an error retrieving your " +
|
||||
resp.config.url.includes("recipientDid")
|
||||
? "received"
|
||||
: "given" + " time from the server.";
|
||||
}
|
||||
};
|
||||
|
||||
// load all the time I have given
|
||||
try {
|
||||
const url =
|
||||
const { headers, identity } = await this.getHeadersAndIdentity(
|
||||
this.activeDid,
|
||||
);
|
||||
const givenByUrl =
|
||||
this.apiServer +
|
||||
"/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.error(
|
||||
"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.";
|
||||
}
|
||||
} catch (error) {
|
||||
this.alertTitle = "Error With Server";
|
||||
this.alertMessage = error as string;
|
||||
}
|
||||
|
||||
// load all the time I have received
|
||||
try {
|
||||
const url =
|
||||
const givenToUrl =
|
||||
this.apiServer +
|
||||
"/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.error(
|
||||
"Got bad response status & data of",
|
||||
resp.status,
|
||||
resp.data
|
||||
);
|
||||
this.alertTitle = "Error With Server";
|
||||
this.alertMessage =
|
||||
"Got an error retrieving your received time from the server.";
|
||||
}
|
||||
|
||||
const [givenByMeResp, givenToMeResp] = await Promise.all([
|
||||
this.axios.get(givenByUrl, { headers }),
|
||||
this.axios.get(givenToUrl, { headers }),
|
||||
]);
|
||||
|
||||
const givenByMeDescriptions = {};
|
||||
const givenByMeConfirmed = {};
|
||||
const givenByMeUnconfirmed = {};
|
||||
handleResponse(
|
||||
givenByMeResp,
|
||||
givenByMeDescriptions,
|
||||
givenByMeConfirmed,
|
||||
givenByMeUnconfirmed,
|
||||
);
|
||||
this.givenByMeDescriptions = givenByMeDescriptions;
|
||||
this.givenByMeConfirmed = givenByMeConfirmed;
|
||||
this.givenByMeUnconfirmed = givenByMeUnconfirmed;
|
||||
|
||||
const givenToMeDescriptions = {};
|
||||
const givenToMeConfirmed = {};
|
||||
const givenToMeUnconfirmed = {};
|
||||
handleResponse(
|
||||
givenToMeResp,
|
||||
givenToMeDescriptions,
|
||||
givenToMeConfirmed,
|
||||
givenToMeUnconfirmed,
|
||||
);
|
||||
this.givenToMeDescriptions = givenToMeDescriptions;
|
||||
this.givenToMeConfirmed = givenToMeConfirmed;
|
||||
this.givenToMeUnconfirmed = givenToMeUnconfirmed;
|
||||
} catch (error) {
|
||||
this.alertTitle = "Error With Server";
|
||||
this.alertMessage = error as string;
|
||||
@@ -403,7 +396,7 @@ export default class ContactsView extends Vue {
|
||||
const allContacts = this.contacts.concat([newContact]);
|
||||
this.contacts = R.sort(
|
||||
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
||||
allContacts
|
||||
allContacts,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -414,7 +407,7 @@ export default class ContactsView extends Vue {
|
||||
this.nameForDid(this.contacts, contact.did) +
|
||||
" with DID " +
|
||||
contact.did +
|
||||
" ?"
|
||||
" ?",
|
||||
)
|
||||
) {
|
||||
await db.open();
|
||||
@@ -428,18 +421,11 @@ export default class ContactsView extends Vue {
|
||||
confirm(
|
||||
"Are you sure you want to use one of your registrations for " +
|
||||
this.nameForDid(this.contacts, contact.did) +
|
||||
"?"
|
||||
"?",
|
||||
)
|
||||
) {
|
||||
await accountsDB.open();
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
|
||||
// Make a claim
|
||||
const vcClaim: RegisterVerifiableCredential = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "RegisterAction",
|
||||
@@ -519,19 +505,8 @@ export default class ContactsView extends Vue {
|
||||
this.apiServer +
|
||||
"/api/report/" +
|
||||
(visibility ? "canSeeMe" : "cannotSeeMe");
|
||||
await accountsDB.open();
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
const headers = await this.getHeaders(identity);
|
||||
const payload = JSON.stringify({ did: contact.did });
|
||||
|
||||
try {
|
||||
@@ -559,19 +534,6 @@ export default class ContactsView extends Vue {
|
||||
this.apiServer +
|
||||
"/api/report/canDidExplicitlySeeMe?did=" +
|
||||
encodeURIComponent(contact.did);
|
||||
await accountsDB.open();
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
|
||||
try {
|
||||
const resp = await this.axios.get(url, { headers });
|
||||
@@ -616,13 +578,7 @@ 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 || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
|
||||
// if they have unconfirmed amounts, ask to confirm those first
|
||||
if (toDid == identity?.did && this.givenToMeUnconfirmed[fromDid] > 0) {
|
||||
@@ -631,7 +587,7 @@ export default class ContactsView extends Vue {
|
||||
"There are " +
|
||||
this.givenToMeUnconfirmed[fromDid] +
|
||||
" unconfirmed hours from them." +
|
||||
" Would you like to confirm some of those hours?"
|
||||
" Would you like to confirm some of those hours?",
|
||||
)
|
||||
) {
|
||||
this.$router.push({
|
||||
@@ -671,7 +627,7 @@ export default class ContactsView extends Vue {
|
||||
" hours " +
|
||||
toFrom +
|
||||
description +
|
||||
"?"
|
||||
"?",
|
||||
)
|
||||
) {
|
||||
this.createAndSubmitGive(
|
||||
@@ -679,7 +635,7 @@ export default class ContactsView extends Vue {
|
||||
fromDid,
|
||||
toDid,
|
||||
parseFloat(this.hourInput),
|
||||
this.hourDescriptionInput
|
||||
this.hourDescriptionInput,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -690,7 +646,7 @@ export default class ContactsView extends Vue {
|
||||
fromDid: string,
|
||||
toDid: string,
|
||||
amount: number,
|
||||
description: string
|
||||
description: string,
|
||||
): Promise<void> {
|
||||
// Make a claim
|
||||
const vcClaim: GiveVerifiableCredential = {
|
||||
@@ -783,10 +739,6 @@ export default class ContactsView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
// This same popup code is in many files.
|
||||
alertTitle = "";
|
||||
alertMessage = "";
|
||||
|
||||
public showGiveAmountsClassNames() {
|
||||
return {
|
||||
"bg-slate-500": this.showGiveTotals,
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
@click="searchLocal()"
|
||||
class="inline-block py-3 rounded-t-lg border-b-2 active text-blue-600 border-blue-600 font-semibold"
|
||||
>
|
||||
Nearby
|
||||
@@ -57,7 +58,14 @@
|
||||
<!-- Results List -->
|
||||
<ul class="">
|
||||
<li class="border-b border-slate-300">
|
||||
<a href="project-view.html" class="block py-4 flex gap-4">
|
||||
<a
|
||||
@click="
|
||||
onClickLoadProject(
|
||||
'https://endorser.ch/entity/01H3HER4DTNCR6ZC4VXA3VPWVQ',
|
||||
)
|
||||
"
|
||||
class="block py-4 flex gap-4"
|
||||
>
|
||||
<div class="w-12">
|
||||
<img
|
||||
src="https://picsum.photos/200/200?random=1"
|
||||
@@ -75,7 +83,14 @@
|
||||
</li>
|
||||
|
||||
<li class="border-b border-slate-300">
|
||||
<a href="project-view.html" class="block py-4 flex gap-4">
|
||||
<a
|
||||
@click="
|
||||
onClickLoadProject(
|
||||
'https://endorser.ch/entity/01H3HER4DTNCR6ZC4VXA3VPWVQ',
|
||||
)
|
||||
"
|
||||
class="block py-4 flex gap-4"
|
||||
>
|
||||
<div class="w-12">
|
||||
<img
|
||||
src="https://picsum.photos/200/200?random=2"
|
||||
@@ -93,7 +108,14 @@
|
||||
</li>
|
||||
|
||||
<li class="border-b border-slate-300">
|
||||
<a href="project-view.html" class="block py-4 flex gap-4">
|
||||
<a
|
||||
@click="
|
||||
onClickLoadProject(
|
||||
'https://endorser.ch/entity/01H3HER4DTNCR6ZC4VXA3VPWVQ',
|
||||
)
|
||||
"
|
||||
class="block py-4 flex gap-4"
|
||||
>
|
||||
<div class="w-12">
|
||||
<img
|
||||
src="https://picsum.photos/200/200?random=3"
|
||||
@@ -123,7 +145,6 @@ import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
import { accountsDB, db } from "@/db";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import * as R from "ramda";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import AlertMessage from "@/components/AlertMessage";
|
||||
import QuickNav from "@/components/QuickNav";
|
||||
@@ -135,6 +156,8 @@ export default class DiscoverView extends Vue {
|
||||
activeDid = "";
|
||||
apiServer = "";
|
||||
searchTerms = "";
|
||||
alertMessage = "";
|
||||
alertTitle = "";
|
||||
|
||||
async mounted() {
|
||||
await db.open();
|
||||
@@ -143,53 +166,112 @@ export default class DiscoverView extends Vue {
|
||||
this.apiServer = settings?.apiServer || "";
|
||||
}
|
||||
|
||||
public async search() {
|
||||
public async buildHeaders() {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
|
||||
if (this.activeDid) {
|
||||
await accountsDB.open();
|
||||
const allAccounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === this.activeDid, allAccounts);
|
||||
//console.log("about to parse from", this.activeDid, account?.identity);
|
||||
const account = allAccounts.find((acc) => acc.did === this.activeDid);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
throw new Error(
|
||||
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service.",
|
||||
);
|
||||
}
|
||||
const token = await accessToken(identity);
|
||||
headers["Authorization"] = "Bearer " + token;
|
||||
|
||||
headers["Authorization"] = "Bearer " + (await accessToken(identity));
|
||||
} else {
|
||||
// it's OK without auth... we just won't get any identifiers
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
public async search() {
|
||||
const claimContents =
|
||||
"claimContents=" + encodeURIComponent(this.searchTerms);
|
||||
const claimType = "claimType=PlanAction";
|
||||
const queryParams = [claimContents, claimType].join("&");
|
||||
return fetch(this.apiServer + "/api/v2/report/claims?" + queryParams, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
})
|
||||
.then(async (response) => {
|
||||
if (response.status !== 200) {
|
||||
const details = await response.text();
|
||||
throw details;
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((results) => {
|
||||
if (results.data) {
|
||||
console.log(results.data);
|
||||
} else {
|
||||
throw JSON.stringify(results);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log("Error with feed load:", e);
|
||||
this.alertMessage =
|
||||
e.userMessage || "There was an error retrieving projects.";
|
||||
this.alertTitle = "Error";
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
this.apiServer + "/api/v2/report/claims?" + queryParams,
|
||||
{
|
||||
method: "GET",
|
||||
headers: await this.buildHeaders(),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.status !== 200) {
|
||||
const details = await response.text();
|
||||
throw details;
|
||||
}
|
||||
|
||||
const results = await response.json();
|
||||
|
||||
if (results.data) {
|
||||
console.log("Plans found in that search:", results.data);
|
||||
} else {
|
||||
throw JSON.stringify(results);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error with feed load:", e);
|
||||
this.alertMessage =
|
||||
e.userMessage || "There was an error retrieving projects.";
|
||||
this.alertTitle = "Error";
|
||||
}
|
||||
}
|
||||
|
||||
alertMessage = "";
|
||||
alertTitle = "";
|
||||
public async searchLocal() {
|
||||
const claimContents =
|
||||
"claimContents=" + encodeURIComponent(this.searchTerms);
|
||||
const queryParams = [
|
||||
claimContents,
|
||||
"minLocLat=40.901000",
|
||||
"maxLocLat=40.904000",
|
||||
"westLocLon=-111.914000",
|
||||
"eastLocLon=-111.909000",
|
||||
].join("&");
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
this.apiServer + "/api/v2/report/plansByLocation?" + queryParams,
|
||||
{
|
||||
method: "GET",
|
||||
headers: await this.buildHeaders(),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw await response.text();
|
||||
}
|
||||
|
||||
const results = await response.json();
|
||||
|
||||
if (results.data) {
|
||||
console.log("Plans found in that location:", results.data);
|
||||
} else {
|
||||
throw JSON.stringify(results);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error with feed load:", e);
|
||||
this.alertMessage =
|
||||
e.userMessage || "There was an error retrieving projects.";
|
||||
this.alertTitle = "Error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clicking on a project entry found in the list
|
||||
* @param id of the project
|
||||
**/
|
||||
onClickLoadProject(id: string) {
|
||||
localStorage.setItem("projectId", id);
|
||||
const route = {
|
||||
name: "project",
|
||||
};
|
||||
this.$router.push(route);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
@click="openDialog(contact)"
|
||||
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||
>
|
||||
{{ contact.name }}
|
||||
{{ contact.name || "(no name)" }}
|
||||
</button>
|
||||
<span v-if="allContacts.length > 0"> or </span>
|
||||
<button @click="openDialog()" class="text-blue-500">
|
||||
@@ -62,19 +62,18 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<AlertMessage
|
||||
:alertTitle="alertTitle"
|
||||
:alertMessage="alertMessage"
|
||||
></AlertMessage>
|
||||
</section>
|
||||
<AlertMessage
|
||||
:alertTitle="alertTitle"
|
||||
:alertMessage="alertMessage"
|
||||
></AlertMessage>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import * as R from "ramda";
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||
import { db, accountsDB } from "@/db";
|
||||
import { AccountsSchema } from "@/db/tables/accounts";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import { createAndSubmitGive, didInfo } from "@/libs/endorserServer";
|
||||
@@ -83,7 +82,7 @@ import { Contact } from "@/db/tables/contacts";
|
||||
import AlertMessage from "@/components/AlertMessage";
|
||||
import QuickNav from "@/components/QuickNav";
|
||||
|
||||
@Options({
|
||||
@Component({
|
||||
components: { GiftedDialog, AlertMessage, QuickNav },
|
||||
})
|
||||
export default class HomeView extends Vue {
|
||||
@@ -98,8 +97,40 @@ export default class HomeView extends Vue {
|
||||
isHiddenSpinner = true;
|
||||
alertTitle = "";
|
||||
alertMessage = "";
|
||||
accounts: AccountsSchema;
|
||||
numAccounts = 0;
|
||||
|
||||
async beforeCreate() {
|
||||
accountsDB.open();
|
||||
this.accounts = accountsDB.accounts;
|
||||
this.numAccounts = await this.accounts.count();
|
||||
}
|
||||
|
||||
public async getIdentity(activeDid) {
|
||||
await accountsDB.open();
|
||||
const account = await accountsDB.accounts
|
||||
.where("did")
|
||||
.equals(activeDid)
|
||||
.first();
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
throw new Error(
|
||||
"Attempted to load Give records with no identity available.",
|
||||
);
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
public async getHeaders(identity) {
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
// 'created' hook runs when the Vue instance is first created
|
||||
async created() {
|
||||
try {
|
||||
await accountsDB.open();
|
||||
@@ -119,14 +150,34 @@ export default class HomeView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
updateAllFeed = async () => {
|
||||
this.isHiddenSpinner = false;
|
||||
public async buildHeaders() {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
|
||||
if (this.activeDid) {
|
||||
await accountsDB.open();
|
||||
const allAccounts = await accountsDB.accounts.toArray();
|
||||
const account = allAccounts.find((acc) => acc.did === this.activeDid);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
throw new Error(
|
||||
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service.",
|
||||
);
|
||||
}
|
||||
|
||||
headers["Authorization"] = "Bearer " + (await accessToken(identity));
|
||||
} else {
|
||||
// it's OK without auth... we just won't get any identifiers
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
public async updateAllFeed() {
|
||||
this.isHiddenSpinner = false;
|
||||
await this.retrieveClaims(this.apiServer, null, this.feedPreviousOldestId)
|
||||
.then(async (results) => {
|
||||
if (results.data.length > 0) {
|
||||
this.feedData = this.feedData.concat(results.data);
|
||||
//console.log("Feed data:", this.feedData);
|
||||
this.feedAllLoaded = results.hitLimit;
|
||||
this.feedPreviousOldestId =
|
||||
results.data[results.data.length - 1].jwtId;
|
||||
@@ -134,7 +185,6 @@ export default class HomeView extends Vue {
|
||||
this.feedLastViewedId == null ||
|
||||
this.feedLastViewedId < results.data[0].jwtId
|
||||
) {
|
||||
// save it to storage
|
||||
await db.open();
|
||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
lastViewedClaimId: results.data[0].jwtId,
|
||||
@@ -151,64 +201,59 @@ export default class HomeView extends Vue {
|
||||
});
|
||||
|
||||
this.isHiddenSpinner = true;
|
||||
};
|
||||
}
|
||||
|
||||
retrieveClaims = async (endorserApiServer, identifier, beforeId) => {
|
||||
//const afterQuery = afterId == null ? "" : "&afterId=" + afterId;
|
||||
public async retrieveClaims(endorserApiServer, identifier, beforeId) {
|
||||
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
if (this.activeDid) {
|
||||
const account = R.find(
|
||||
(acc) => acc.did === this.activeDid,
|
||||
this.allAccounts
|
||||
);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
const token = await accessToken(identity);
|
||||
headers["Authorization"] = "Bearer " + token;
|
||||
} else {
|
||||
// it's OK without auth... we just won't get any identifiers
|
||||
const response = await fetch(
|
||||
endorserApiServer + "/api/v2/report/gives?" + beforeQuery,
|
||||
{
|
||||
method: "GET",
|
||||
headers: await this.buildHeaders(),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw await response.text();
|
||||
}
|
||||
return fetch(this.apiServer + "/api/v2/report/gives?" + beforeQuery, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
})
|
||||
.then(async (response) => {
|
||||
if (response.status !== 200) {
|
||||
const details = await response.text();
|
||||
throw details;
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((results) => {
|
||||
if (results.data) {
|
||||
return results;
|
||||
} else {
|
||||
throw JSON.stringify(results);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const results = await response.json();
|
||||
|
||||
if (results.data) {
|
||||
return results;
|
||||
} else {
|
||||
throw JSON.stringify(results);
|
||||
}
|
||||
}
|
||||
|
||||
giveDescription(giveRecord) {
|
||||
let claim = giveRecord.fullClaim;
|
||||
if (claim.claim) {
|
||||
// it's probably a Verified Credential
|
||||
claim = claim.claim;
|
||||
}
|
||||
|
||||
// agent.did is for legacy data, before March 2023
|
||||
const giver =
|
||||
const giverDid =
|
||||
claim.agent?.identifier || claim.agent?.did || giveRecord.issuer;
|
||||
const giverInfo = didInfo(giver, this.allAccounts, this.allContacts);
|
||||
const giverInfo = didInfo(
|
||||
giverDid,
|
||||
this.activeDid,
|
||||
this.allAccounts,
|
||||
this.allContacts,
|
||||
);
|
||||
const gaveAmount = claim.object?.amountOfThisGood
|
||||
? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
|
||||
: claim.description || "something unknown";
|
||||
// recipient.did is for legacy data, before March 2023
|
||||
const gaveRecipientId = claim.recipient?.identifier || claim.recipient?.did;
|
||||
const gaveRecipientInfo = gaveRecipientId
|
||||
? " to " + didInfo(gaveRecipientId, this.allAccounts, this.allContacts)
|
||||
? " to " +
|
||||
didInfo(
|
||||
gaveRecipientId,
|
||||
this.activeDid,
|
||||
this.allAccounts,
|
||||
this.allContacts,
|
||||
)
|
||||
: "";
|
||||
return giverInfo + " gave " + gaveAmount + gaveRecipientInfo;
|
||||
}
|
||||
@@ -224,6 +269,7 @@ export default class HomeView extends Vue {
|
||||
openDialog(giver) {
|
||||
this.$refs.customDialog.open(giver);
|
||||
}
|
||||
|
||||
handleDialogResult(result) {
|
||||
if (result.action === "confirm") {
|
||||
return new Promise((resolve) => {
|
||||
@@ -241,59 +287,71 @@ export default class HomeView extends Vue {
|
||||
* @param description may be an empty string
|
||||
* @param hours may be 0
|
||||
*/
|
||||
recordGive(giverDid, description, hours) {
|
||||
if (this.activeDid == null) {
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage =
|
||||
"You must select an identity before you can record a give.";
|
||||
public async recordGive(giverDid, description, hours) {
|
||||
if (!this.activeDid) {
|
||||
this.setAlert(
|
||||
"Error",
|
||||
"You must select an identity before you can record a give.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!description && !hours) {
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage =
|
||||
"You must enter a description or some number of hours.";
|
||||
this.setAlert(
|
||||
"Error",
|
||||
"You must enter a description or some number of hours.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const account = R.find(
|
||||
(acc) => acc.did === this.activeDid,
|
||||
this.allAccounts
|
||||
);
|
||||
//console.log("about to parse from", this.activeDid, account?.identity);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
|
||||
try {
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
const result = await createAndSubmitGive(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
identity,
|
||||
giverDid,
|
||||
this.activeDid,
|
||||
description,
|
||||
hours,
|
||||
);
|
||||
|
||||
if (isGiveCreationError(result)) {
|
||||
const errorMessage = getGiveCreationErrorMessage(result);
|
||||
console.log("Error with give result:", result);
|
||||
this.setAlert(
|
||||
"Error",
|
||||
errorMessage || "There was an error recording the give.",
|
||||
);
|
||||
} else {
|
||||
this.setAlert("Success", "That gift was recorded.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error with give caught:", error);
|
||||
this.setAlert(
|
||||
"Error",
|
||||
getGiveErrorMessage(error) || "There was an error recording the give.",
|
||||
);
|
||||
}
|
||||
createAndSubmitGive(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
identity,
|
||||
giverDid,
|
||||
this.activeDid,
|
||||
description,
|
||||
hours
|
||||
)
|
||||
.then((result) => {
|
||||
if (result.status != 201 || result.data?.error) {
|
||||
console.log("Error with give result:", result);
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage =
|
||||
result.data?.error?.message ||
|
||||
"There was an error recording the give.";
|
||||
} else {
|
||||
this.alertTitle = "Success";
|
||||
this.alertMessage = "That gift was recorded.";
|
||||
//this.updateAllFeed(); // full update is overkill but we should show something
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
// axios throws errors on 400 responses
|
||||
console.log("Error with give caught:", e);
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage =
|
||||
e.userMessage ||
|
||||
e.response?.data?.error?.message ||
|
||||
"There was an error recording the give.";
|
||||
});
|
||||
}
|
||||
|
||||
private setAlert(title, message) {
|
||||
this.alertTitle = title;
|
||||
this.alertMessage = message;
|
||||
}
|
||||
|
||||
// Helper functions for readability
|
||||
|
||||
isGiveCreationError(result) {
|
||||
return result.status !== 201 || result.data?.error;
|
||||
}
|
||||
|
||||
getGiveCreationErrorMessage(result) {
|
||||
return result.data?.error?.message;
|
||||
}
|
||||
|
||||
getGiveErrorMessage(error) {
|
||||
return error.userMessage || error.response?.data?.error?.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -43,12 +43,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
||||
import { accountsDB, db } from "@/db";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
|
||||
@Options({
|
||||
@Component({
|
||||
components: {},
|
||||
})
|
||||
export default class ImportAccountView extends Vue {
|
||||
@@ -72,7 +72,7 @@ export default class ImportAccountView extends Vue {
|
||||
this.address,
|
||||
this.publicHex,
|
||||
this.privateHex,
|
||||
this.derivationPath
|
||||
this.derivationPath,
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
@@ -49,11 +49,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { db } from "@/db";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
|
||||
@Options({
|
||||
@Component({
|
||||
components: {},
|
||||
})
|
||||
export default class NewEditAccountView extends Vue {
|
||||
|
||||
@@ -61,9 +61,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
@Options({
|
||||
@Component({
|
||||
components: {},
|
||||
})
|
||||
export default class NewEditCommitmentView extends Vue {}
|
||||
|
||||
@@ -73,10 +73,10 @@
|
||||
<script lang="ts">
|
||||
import { AxiosError } from "axios";
|
||||
import * as didJwt from "did-jwt";
|
||||
import * as R from "ramda";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
import { accountsDB, db } from "@/db";
|
||||
import { AccountsSchema } from "@/db/tables/accounts";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||
import { useAppStore } from "@/store/app";
|
||||
@@ -100,12 +100,44 @@ export default class NewEditProjectView extends Vue {
|
||||
projectName = "";
|
||||
description = "";
|
||||
errorMessage = "";
|
||||
accounts: AccountsSchema;
|
||||
numAccounts = 0;
|
||||
|
||||
async beforeCreate() {
|
||||
accountsDB.open();
|
||||
this.accounts = accountsDB.accounts;
|
||||
this.numAccounts = await this.accounts.count();
|
||||
}
|
||||
|
||||
public async getIdentity(activeDid) {
|
||||
await accountsDB.open();
|
||||
const account = await accountsDB.accounts
|
||||
.where("did")
|
||||
.equals(activeDid)
|
||||
.first();
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
throw new Error(
|
||||
"Attempted to load project records with no identity available.",
|
||||
);
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
public async getHeaders(identity) {
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
projectId = localStorage.getItem("projectId") || "";
|
||||
isHiddenSave = false;
|
||||
isHiddenSpinner = true;
|
||||
|
||||
// 'created' hook runs when the Vue instance is first created
|
||||
async created() {
|
||||
await db.open();
|
||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||
@@ -113,16 +145,14 @@ export default class NewEditProjectView extends Vue {
|
||||
this.apiServer = settings?.apiServer || "";
|
||||
|
||||
if (this.projectId) {
|
||||
await accountsDB.open();
|
||||
const num_accounts = await accountsDB.accounts.count();
|
||||
if (num_accounts === 0) {
|
||||
if (this.numAccounts === 0) {
|
||||
console.error("Error: no account was found.");
|
||||
} else {
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
throw new Error(
|
||||
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service.",
|
||||
);
|
||||
}
|
||||
this.LoadProject(identity);
|
||||
}
|
||||
@@ -205,7 +235,7 @@ export default class NewEditProjectView extends Vue {
|
||||
// handleId is new in server v release-1.6.0; remove fullIri when that
|
||||
// version shows up here: https://endorser.ch:3000/api-docs/
|
||||
useAppStore().setProjectId(
|
||||
resp.data.success.handleId || resp.data.success.fullIri
|
||||
resp.data.success.handleId || resp.data.success.fullIri,
|
||||
);
|
||||
setTimeout(
|
||||
function (that: Vue) {
|
||||
@@ -215,7 +245,7 @@ export default class NewEditProjectView extends Vue {
|
||||
that.$router.push(route);
|
||||
},
|
||||
2000,
|
||||
this
|
||||
this,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -234,7 +264,7 @@ export default class NewEditProjectView extends Vue {
|
||||
} else {
|
||||
console.error(
|
||||
"Here's the full error trying to save the claim:",
|
||||
error
|
||||
error,
|
||||
);
|
||||
this.alertTitle = "Claim Error";
|
||||
this.alertMessage = error as string;
|
||||
@@ -248,17 +278,11 @@ export default class NewEditProjectView extends Vue {
|
||||
public async onSaveProjectClick() {
|
||||
this.isHiddenSave = true;
|
||||
this.isHiddenSpinner = false;
|
||||
await accountsDB.open();
|
||||
const num_accounts = await accountsDB.accounts.count();
|
||||
if (num_accounts === 0) {
|
||||
|
||||
if (this.numAccounts === 0) {
|
||||
console.error("Error: there is no account.");
|
||||
} else {
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
this.SaveProject(identity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="text-red-500">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
@@ -65,77 +65,90 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="openDialog({ name: 'you', did: activeDid })"
|
||||
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-8"
|
||||
>
|
||||
I gave...
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<p>... or choose a contact who gave:</p>
|
||||
<!-- similar contact selection code is in multiple places -->
|
||||
<div class="px-4">
|
||||
<div v-if="activeDid">
|
||||
<button
|
||||
v-for="contact in allContacts"
|
||||
:key="contact.did"
|
||||
@click="openDialog(contact)"
|
||||
class="text-blue-500"
|
||||
@click="openDialog({ name: 'you', did: activeDid })"
|
||||
class="text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
|
||||
>
|
||||
{{ contact.name }},
|
||||
</button>
|
||||
<span v-if="allContacts.length > 0"> or </span>
|
||||
<button @click="openDialog()" class="text-blue-500">
|
||||
someone not specified
|
||||
I gave...
|
||||
</button>
|
||||
― or:
|
||||
</div>
|
||||
<!-- similar contact selection code is in multiple places -->
|
||||
Record a gift from
|
||||
<span v-for="contact in allContacts" :key="contact.did">
|
||||
<button @click="openDialog(contact)" class="text-blue-500">
|
||||
{{ contact.name }}</button
|
||||
>,
|
||||
</span>
|
||||
<span v-if="allContacts.length > 0"> or </span>
|
||||
<button @click="openDialog()" class="text-blue-500">
|
||||
someone not specified
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Gifts to & from this -->
|
||||
<div class="mt-8 flex justify-around">
|
||||
<div>
|
||||
<h1 class="text-xl">Given to this Project</h1>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-xl">... and from this Project</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-around">
|
||||
<div class="w-1/2">
|
||||
<div v-for="give in givesToThis" :key="give.id">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex gap-3">
|
||||
<div class="flex gap-2">
|
||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||
<span>{{
|
||||
didInfo(give.agentDid, activeDid, accounts, allContacts)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex gap-2" v-if="give.amount">
|
||||
<fa icon="coins" class="fa-fw text-slate-400"></fa>
|
||||
<span>{{ give.amount }}</span>
|
||||
</div>
|
||||
<div class="flex gap-2" v-if="give.description">
|
||||
<fa icon="comment" class="fa-fw text-slate-400"></fa>
|
||||
<span>{{ give.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<div v-for="give in givesByThis" :key="give.id">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex gap-3">
|
||||
<div class="flex gap-2">
|
||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||
<span>{{
|
||||
didInfo(give.agentDid, activeDid, accounts, allContacts)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex gap-2" v-if="give.amount">
|
||||
<fa icon="coins" class="fa-fw text-slate-400"></fa>
|
||||
<span>{{ give.amount }}</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<fa icon="comment" class="fa-fw text-slate-400"></fa>
|
||||
<span>{{ give.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<GiftedDialog
|
||||
ref="customDialog"
|
||||
@dialog-result="handleDialogResult"
|
||||
message="Received from"
|
||||
>
|
||||
</GiftedDialog>
|
||||
|
||||
<!-- Commit -->
|
||||
<!--
|
||||
<router-link
|
||||
:to="{ name: 'new-edit-commitment' }"
|
||||
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-8"
|
||||
>Make Commitment</router-link
|
||||
>
|
||||
-->
|
||||
|
||||
<!-- Commitments -->
|
||||
<!--
|
||||
<div class="bg-slate-100 px-4 py-3 rounded-md">
|
||||
<h3 class="text-sm uppercase font-semibold mb-3">Commitments</h3>
|
||||
|
||||
<ul class="text-sm border-t border-slate-300">
|
||||
<li class="flex justify-between gap-4 py-1.5 border-b border-slate-300">
|
||||
<span>[Username]</span>
|
||||
<span
|
||||
>5 hours <fa icon="spinner" class="fa-fw text-slate-400"></fa
|
||||
></span>
|
||||
</li>
|
||||
|
||||
<li class="flex justify-between gap-4 py-1.5 border-b border-slate-300">
|
||||
<span>[Username]</span>
|
||||
<span
|
||||
>US$ 20.00 <fa icon="circle-check" class="fa-fw text-lime-500"></fa
|
||||
></span>
|
||||
</li>
|
||||
|
||||
<li class="flex justify-between gap-4 py-1.5 border-b border-slate-300">
|
||||
<span>[Username]</span>
|
||||
<span
|
||||
>0.1 BTC <fa icon="spinner" class="fa-fw text-slate-400"></fa
|
||||
></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
-->
|
||||
<AlertMessage
|
||||
:alertTitle="alertTitle"
|
||||
:alertMessage="alertMessage"
|
||||
@@ -146,16 +159,20 @@
|
||||
<script lang="ts">
|
||||
import { AxiosError } from "axios";
|
||||
import * as moment from "moment";
|
||||
import * as R from "ramda";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||
import { accountsDB, db } from "@/db";
|
||||
import { AccountsSchema } from "@/db/tables/accounts";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { createAndSubmitGive } from "@/libs/endorserServer";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
import {
|
||||
createAndSubmitGive,
|
||||
didInfo,
|
||||
GiveServerRecord,
|
||||
} from "@/libs/endorserServer";
|
||||
import AlertMessage from "@/components/AlertMessage";
|
||||
import QuickNav from "@/components/QuickNav";
|
||||
|
||||
@@ -163,17 +180,68 @@ import QuickNav from "@/components/QuickNav";
|
||||
components: { GiftedDialog, AlertMessage, QuickNav },
|
||||
})
|
||||
export default class ProjectViewView extends Vue {
|
||||
accounts: AccountsSchema;
|
||||
activeDid = "";
|
||||
alertMessage = "";
|
||||
alertTitle = "";
|
||||
allContacts: Array<Contact> = [];
|
||||
apiServer = "";
|
||||
expanded = false;
|
||||
name = "";
|
||||
description = "";
|
||||
errorMessage = "";
|
||||
expanded = false;
|
||||
givesToThis: Array<GiveServerRecord> = [];
|
||||
givesByThis: Array<GiveServerRecord> = [];
|
||||
name = "";
|
||||
numAccounts = 0;
|
||||
projectId = localStorage.getItem("projectId") || ""; // handle ID
|
||||
timeSince = "";
|
||||
truncatedDesc = "";
|
||||
truncateLength = 40;
|
||||
timeSince = "";
|
||||
projectId = localStorage.getItem("projectId") || ""; // handle ID
|
||||
errorMessage = "";
|
||||
|
||||
async beforeCreate() {
|
||||
accountsDB.open();
|
||||
this.accounts = accountsDB.accounts;
|
||||
this.numAccounts = (await this.accounts?.count()) || 0;
|
||||
}
|
||||
|
||||
async created() {
|
||||
await db.open();
|
||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||
this.activeDid = settings?.activeDid || "";
|
||||
this.apiServer = settings?.apiServer || "";
|
||||
this.allContacts = await db.contacts.toArray();
|
||||
|
||||
this.accounts = accountsDB.accounts;
|
||||
const accountsArr = await this.accounts?.toArray();
|
||||
const account = accountsArr.find((acc) => acc.did === this.activeDid);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
this.LoadProject(identity);
|
||||
}
|
||||
|
||||
public async getIdentity(activeDid) {
|
||||
await accountsDB.open();
|
||||
const account = await accountsDB.accounts
|
||||
.where("did")
|
||||
.equals(activeDid)
|
||||
.first();
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
throw new Error(
|
||||
"Attempted to load project records with no identity available.",
|
||||
);
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
public async getHeaders(identity) {
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
onEditClick() {
|
||||
localStorage.setItem("projectId", this.projectId as string);
|
||||
@@ -183,6 +251,11 @@ export default class ProjectViewView extends Vue {
|
||||
this.$router.push(route);
|
||||
}
|
||||
|
||||
// Isn't there a better way to make this available to the template?
|
||||
didInfo(did, activeDid, identities, contacts) {
|
||||
return didInfo(did, activeDid, identities, contacts);
|
||||
}
|
||||
|
||||
expandText() {
|
||||
this.expanded = true;
|
||||
}
|
||||
@@ -196,15 +269,19 @@ export default class ProjectViewView extends Vue {
|
||||
this.apiServer +
|
||||
"/api/claim/byHandle/" +
|
||||
encodeURIComponent(this.projectId);
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
if (identity) {
|
||||
const token = await accessToken(identity);
|
||||
headers["Authorization"] = "Bearer " + token;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await this.axios.get(url, { headers });
|
||||
if (resp.status === 200) {
|
||||
// feel free to remove this; I haven't yet because it's helpful
|
||||
console.log("Loaded project: ", resp.data);
|
||||
const startTime = resp.data.startTime;
|
||||
if (startTime != null) {
|
||||
const eventDate = new Date(startTime);
|
||||
@@ -229,34 +306,45 @@ export default class ProjectViewView extends Vue {
|
||||
console.error("Error retrieving project:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 'created' hook runs when the Vue instance is first created
|
||||
async created() {
|
||||
await db.open();
|
||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||
this.activeDid = settings?.activeDid || "";
|
||||
this.apiServer = settings?.apiServer || "";
|
||||
this.allContacts = await db.contacts.toArray();
|
||||
|
||||
await accountsDB.open();
|
||||
const num_accounts = await accountsDB.accounts.count();
|
||||
if (num_accounts === 0) {
|
||||
console.error("Problem! Should have a profile!");
|
||||
} else {
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
const givesInUrl =
|
||||
this.apiServer +
|
||||
"/api/v2/report/givesForPlans?planIds=" +
|
||||
encodeURIComponent(JSON.stringify([this.projectId]));
|
||||
try {
|
||||
const resp = await this.axios.get(givesInUrl, { headers });
|
||||
if (resp.status === 200 && resp.data.data) {
|
||||
this.givesToThis = resp.data.data;
|
||||
} else {
|
||||
this.errorMessage = "Failed to retrieve gives to this project.";
|
||||
}
|
||||
this.LoadProject(identity);
|
||||
} catch (error: unknown) {
|
||||
console.error("Error retrieving gives to this project:", error);
|
||||
this.errorMessage =
|
||||
"Something went wrong retrieving gives to this project.";
|
||||
}
|
||||
|
||||
const givesOutUrl =
|
||||
this.apiServer +
|
||||
"/api/v2/report/givesProvidedBy?providerId=" +
|
||||
encodeURIComponent(this.projectId);
|
||||
try {
|
||||
const resp = await this.axios.get(givesOutUrl, { headers });
|
||||
if (resp.status === 200 && resp.data.data) {
|
||||
this.givesByThis = resp.data.data;
|
||||
} else {
|
||||
this.errorMessage = "Failed to retrieve gives by this project.";
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error("Error retrieving gives by this project:", error);
|
||||
this.errorMessage = "Something went wrong retrieving gives by project.";
|
||||
}
|
||||
}
|
||||
|
||||
openDialog(contact) {
|
||||
this.$refs.customDialog.open(contact);
|
||||
}
|
||||
|
||||
handleDialogResult(result) {
|
||||
if (result.action === "confirm") {
|
||||
return new Promise((resolve) => {
|
||||
@@ -275,60 +363,51 @@ export default class ProjectViewView extends Vue {
|
||||
* @param hours may be 0
|
||||
*/
|
||||
async recordGive(giverDid, description, hours) {
|
||||
if (this.activeDid == null) {
|
||||
if (!this.activeDid) {
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage =
|
||||
"You must select an identity before you can record a give.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!description && !hours) {
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage =
|
||||
"You must enter a description or some number of hours.";
|
||||
return;
|
||||
}
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === this.activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
createAndSubmitGive(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
identity,
|
||||
giverDid,
|
||||
this.activeDid,
|
||||
description,
|
||||
hours,
|
||||
this.projectId
|
||||
)
|
||||
.then((result) => {
|
||||
if (result.status != 201 || result.data?.error) {
|
||||
console.log("Error with give result:", result);
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage =
|
||||
result.data?.error?.message ||
|
||||
"There was an error recording the give.";
|
||||
} else {
|
||||
this.alertTitle = "Success";
|
||||
this.alertMessage = "That gift was recorded.";
|
||||
//this.updateAllFeed(); // full update is overkill but we should show something
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
// axios throws errors on 400 responses
|
||||
console.log("Error with give caught:", e);
|
||||
|
||||
try {
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
const result = await createAndSubmitGive(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
identity,
|
||||
giverDid,
|
||||
this.activeDid,
|
||||
description,
|
||||
hours,
|
||||
this.projectId,
|
||||
);
|
||||
|
||||
if (result.status !== 201 || result.data?.error) {
|
||||
console.log("Error with give result:", result);
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage =
|
||||
e.userMessage ||
|
||||
e.response?.data?.error?.message ||
|
||||
result.data?.error?.message ||
|
||||
"There was an error recording the give.";
|
||||
});
|
||||
} else {
|
||||
this.alertTitle = "Success";
|
||||
this.alertMessage = "That gift was recorded.";
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error with give caught:", e);
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage =
|
||||
e.userMessage ||
|
||||
e.response?.data?.error?.message ||
|
||||
"There was an error recording the give.";
|
||||
}
|
||||
}
|
||||
|
||||
// This same popup code is in many files.
|
||||
alertMessage = "";
|
||||
alertTitle = "";
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -73,9 +73,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import * as R from "ramda";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { accountsDB, db } from "@/db";
|
||||
import { AccountsSchema } from "@/db/tables/accounts";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
@@ -115,6 +115,14 @@ export default class ProjectsView extends Vue {
|
||||
isLoading = false;
|
||||
alertTitle = "";
|
||||
alertMessage = "";
|
||||
accounts: AccountsSchema;
|
||||
numAccounts = 0;
|
||||
|
||||
async beforeCreate() {
|
||||
accountsDB.open();
|
||||
this.accounts = accountsDB.accounts;
|
||||
this.numAccounts = await this.accounts.count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Core project data loader
|
||||
@@ -130,14 +138,15 @@ export default class ProjectsView extends Vue {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
const resp = await this.axios.get(url, { headers });
|
||||
if (resp.status === 200) {
|
||||
if (resp.status === 200 || !resp.data.data) {
|
||||
const plans: ProjectData[] = resp.data.data;
|
||||
for (const plan of plans) {
|
||||
const { name, description, handleId = plan.fullIri, rowid } = plan;
|
||||
this.projects.push({ name, description, handleId, rowid });
|
||||
}
|
||||
} else {
|
||||
console.log(resp.status);
|
||||
console.log("Bad server response & data:", resp.status, resp.data);
|
||||
throw Error("Failed to get projects from the server.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Got error loading projects:", error.message);
|
||||
@@ -183,6 +192,22 @@ export default class ProjectsView extends Vue {
|
||||
await this.dataLoader(url, token);
|
||||
}
|
||||
|
||||
public async getIdentity(activeDid) {
|
||||
await accountsDB.open();
|
||||
const account = await accountsDB.accounts
|
||||
.where("did")
|
||||
.equals(activeDid)
|
||||
.first();
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
throw new Error(
|
||||
"Attempted to load project records with no identity available.",
|
||||
);
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'created' hook runs when the Vue instance is first created
|
||||
**/
|
||||
@@ -193,26 +218,19 @@ export default class ProjectsView extends Vue {
|
||||
const activeDid = settings?.activeDid || "";
|
||||
this.apiServer = settings?.apiServer || "";
|
||||
|
||||
await accountsDB.open();
|
||||
const num_accounts = await accountsDB.accounts.count();
|
||||
if (num_accounts === 0) {
|
||||
console.error("Problem! You need a profile!");
|
||||
this.alertTitle = "Error!";
|
||||
this.alertMessage = "Problem! You need a profile!";
|
||||
if (this.numAccounts === 0) {
|
||||
console.error("No accounts found.");
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage = "You need an identity to load your projects.";
|
||||
} else {
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
const account = R.find((acc) => acc.did === activeDid, accounts);
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
if (!identity) {
|
||||
throw new Error("No identity found.");
|
||||
}
|
||||
const identity = await this.getIdentity(activeDid);
|
||||
this.current = identity;
|
||||
this.LoadProjects(identity);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
this.alertTitle = "Error!";
|
||||
this.alertMessage = "Problem! You need a profile!";
|
||||
console.log("Error initializing:", err);
|
||||
this.alertTitle = "Error";
|
||||
this.alertMessage = "Something went wrong loading your projects.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-class-component";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
@Options({
|
||||
@Component({
|
||||
components: {},
|
||||
})
|
||||
export default class StartView extends Vue {
|
||||
|
||||
@@ -39,15 +39,7 @@
|
||||
:alertMessage="alertMessage"
|
||||
></AlertMessage>
|
||||
</section>
|
||||
</template> /**
|
||||
// from https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#examples
|
||||
// Adds a blank image const dataBlob = document
|
||||
.querySelector("#scene-container") .firstChild.toBlob((blob) => { const newImg =
|
||||
document.createElement("img"); const url = URL.createObjectURL(blob);
|
||||
newImg.onload = () => { // no longer need to read the blob so it's revoked
|
||||
URL.revokeObjectURL(url); }; newImg.src = url;
|
||||
document.body.appendChild(newImg); }); **/
|
||||
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { SVGRenderer } from "three/addons/renderers/SVGRenderer.js";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
Reference in New Issue
Block a user