Merge branch 'master' into photo-upload
This commit is contained in:
4
.env.development
Normal file
4
.env.development
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Only the variables that start with VUE_APP_ are seen in the application process.env in Vue.
|
||||||
|
|
||||||
|
# this won't resolve as a URL on production; it's a URN only found in the test system
|
||||||
|
VUE_APP_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HNTZYJJXTGT0EZS3VEJGX7AK
|
||||||
4
.env.production
Normal file
4
.env.production
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Only the variables that start with VUE_APP_ are seen in the application process.env in Vue.
|
||||||
|
VUE_APP_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01GXYPFF7FA03NXKPYY142PY4H
|
||||||
|
VUE_APP_DEFAULT_ENDORSER_API_SERVER=https://api.endorser.ch
|
||||||
|
VUE_APP_DEFAULT_IMAGE_API_SERVER=https://image-api.timesafari.app
|
||||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -6,13 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Changed in DB
|
### Fixed
|
||||||
- ?
|
- Environment variable for BVC meetings project
|
||||||
|
### Changed in DB or environment
|
||||||
|
- Test that a new browser session will get the right default API
|
||||||
|
- Test that a new browser session will send the right BVC meetings project
|
||||||
|
|
||||||
|
|
||||||
## [0.2.14] - 2024.02.14
|
## [0.2.17] - 2024.03.01 - 3612ea42240c5e1b7d7eff29a39ff18f1b869b36
|
||||||
|
### Added
|
||||||
|
- Shortcut page for Bountiful Voluntaryist Community
|
||||||
### Changed
|
### Changed
|
||||||
- Combine all service worker scripts into a single file
|
- More readable, targeted summaries in home-page feed items
|
||||||
|
### Changed in DB
|
||||||
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.14] - 2024.02.14 - 5f9edea1167dbfb64e16648764eed8c09b24eaeb
|
||||||
|
### Changed
|
||||||
|
- Combine all service worker scripts into a single file.
|
||||||
### Changed in DB
|
### Changed in DB
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -32,21 +32,22 @@ npm run lint
|
|||||||
|
|
||||||
* `npx prettier --write ./sw_scripts/`
|
* `npx prettier --write ./sw_scripts/`
|
||||||
|
|
||||||
* Update the project.task.yaml & CHANGELOG.md & the version in package.json, run `npm install`, and commit.
|
* Update the project.task.yaml & CHANGELOG.md & the version in package.json, run `npm install`.
|
||||||
|
|
||||||
* [Tag wth the new version.](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases)
|
* If production: change package.json to remove "_Test". Also record what version is on production.
|
||||||
|
|
||||||
... though maybe you do that after testing and release, since that isn't used in the build (and you often increment a lot during testing).
|
* `npm run build-dev` for test servers or `npm run build` for production.
|
||||||
|
|
||||||
* If production: change src/constants/app.ts DEFAULT_*_SERVER to be "PROD" and package.json to remove "_Test". Also record what version is on production.
|
|
||||||
|
|
||||||
* `npm run build`
|
|
||||||
|
|
||||||
* Get on the server and back up the time-safari folder.
|
* Get on the server and back up the time-safari folder.
|
||||||
|
|
||||||
* `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari`
|
* `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari`
|
||||||
|
|
||||||
* Revert src/constants/app.ts and package.json (if that was prod), edit package.json to increment version & add "-beta", `npm install`, and commit. Tag if you didn't before. Also record what version is on production.
|
* Revert src/constants/app.ts and package.json (if that was prod).
|
||||||
|
|
||||||
|
* Commit changes. Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
|
||||||
|
|
||||||
|
* [Tag with the new version.](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "TimeSafari_Test",
|
"name": "TimeSafari_Test",
|
||||||
"version": "0.2.15-beta",
|
"version": "0.2.18-beta",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "TimeSafari_Test",
|
"name": "TimeSafari_Test",
|
||||||
"version": "0.2.15-beta",
|
"version": "0.2.18-beta",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dicebear/collection": "^5.3.5",
|
"@dicebear/collection": "^5.3.5",
|
||||||
"@dicebear/core": "^5.3.5",
|
"@dicebear/core": "^5.3.5",
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
||||||
"@tweenjs/tween.js": "^21.0.0",
|
"@tweenjs/tween.js": "^21.0.0",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
|
"@types/luxon": "^3.4.2",
|
||||||
"@veramo/core": "^5.4.1",
|
"@veramo/core": "^5.4.1",
|
||||||
"@veramo/credential-w3c": "^5.4.1",
|
"@veramo/credential-w3c": "^5.4.1",
|
||||||
"@veramo/data-store": "^5.4.1",
|
"@veramo/data-store": "^5.4.1",
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
"js-generate-password": "^0.1.9",
|
"js-generate-password": "^0.1.9",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"localstorage-slim": "^2.5.0",
|
"localstorage-slim": "^2.5.0",
|
||||||
"luxon": "^3.4.3",
|
"luxon": "^3.4.4",
|
||||||
"merkletreejs": "^0.3.11",
|
"merkletreejs": "^0.3.11",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"notiwind": "^2.0.2",
|
"notiwind": "^2.0.2",
|
||||||
@@ -9171,6 +9172,11 @@
|
|||||||
"@types/geojson": "*"
|
"@types/geojson": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/luxon": {
|
||||||
|
"version": "3.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
|
||||||
|
"integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA=="
|
||||||
|
},
|
||||||
"node_modules/@types/mime": {
|
"node_modules/@types/mime": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
|
||||||
@@ -20252,9 +20258,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/luxon": {
|
"node_modules/luxon": {
|
||||||
"version": "3.4.3",
|
"version": "3.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
||||||
"integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==",
|
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "TimeSafari_Test",
|
"name": "TimeSafari_Test",
|
||||||
"version": "0.2.15-beta",
|
"version": "0.2.18-beta",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
|
"build-dev": "vue-cli-service build --mode development",
|
||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
||||||
"@tweenjs/tween.js": "^21.0.0",
|
"@tweenjs/tween.js": "^21.0.0",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
|
"@types/luxon": "^3.4.2",
|
||||||
"@veramo/core": "^5.4.1",
|
"@veramo/core": "^5.4.1",
|
||||||
"@veramo/credential-w3c": "^5.4.1",
|
"@veramo/credential-w3c": "^5.4.1",
|
||||||
"@veramo/data-store": "^5.4.1",
|
"@veramo/data-store": "^5.4.1",
|
||||||
@@ -41,7 +43,7 @@
|
|||||||
"js-generate-password": "^0.1.9",
|
"js-generate-password": "^0.1.9",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"localstorage-slim": "^2.5.0",
|
"localstorage-slim": "^2.5.0",
|
||||||
"luxon": "^3.4.3",
|
"luxon": "^3.4.4",
|
||||||
"merkletreejs": "^0.3.11",
|
"merkletreejs": "^0.3.11",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"notiwind": "^2.0.2",
|
"notiwind": "^2.0.2",
|
||||||
|
|||||||
@@ -5,43 +5,40 @@ tasks :
|
|||||||
- .1 on ideas, put an "x" to close it
|
- .1 on ideas, put an "x" to close it
|
||||||
|
|
||||||
- .2 fix give dialog from "more contacts" off home page to allow giving to this user
|
- .2 fix give dialog from "more contacts" off home page to allow giving to this user
|
||||||
- 01 in the feed, group by project or contact or topic or time/$ (via BC)
|
|
||||||
- .2 anchor hash into BTC
|
|
||||||
|
|
||||||
- .1 when gave to a project, say "gave to project"
|
|
||||||
- .2 fix bottom of project selection map, where the icons are hidden but a tap goes to the icon's page
|
- .2 fix bottom of project selection map, where the icons are hidden but a tap goes to the icon's page
|
||||||
- 01 separate not-on-platform vs totally anonymous; terminology "unidentified"?
|
- .5 stop from seeing an error on the first page when browser doesn't support service workers (which I've seen on iPhone; visible in Firefox private window)
|
||||||
|
- .2 don't show a warning on a totally new project when the authorized agent is set
|
||||||
|
- .2 anchor hash into BTC
|
||||||
|
- .2 list the "show more" contacts alphabetically
|
||||||
|
|
||||||
- 01 page for BVC
|
- 32 image on give :
|
||||||
|
- Show a camera to take a picture
|
||||||
|
- Scale the image to a reasonable size
|
||||||
|
- Upload to a public readable place
|
||||||
|
- check the rate limits
|
||||||
|
- use CID (hash?)
|
||||||
|
- put the image URL in the claim
|
||||||
|
- Rates - images erased?
|
||||||
|
- image not associated with JWT ULID since that's assigned later
|
||||||
|
|
||||||
- 24 compelling UI for credential presentations
|
- 24 compelling UI for credential presentations
|
||||||
- discover who in my network has activity on a project
|
- discover who in my network has activity on a project
|
||||||
|
|
||||||
- 24 compelling UI for statistics (eg. World?)
|
- 24 compelling UI for statistics (eg. World?)
|
||||||
|
|
||||||
- .5 stop from seeing an error on the first page when browser doesn't support service workers (which I've seen on iPhone; visible in Firefox private window)
|
- 01 in the feed, group by project or contact or topic or time/$ (via BC)
|
||||||
|
- 01 separate not-on-platform vs totally anonymous; terminology "unidentified"?
|
||||||
- .2 add links between projects
|
- .2 add links between projects
|
||||||
- 32 image on give :
|
|
||||||
- Show a camera to take a picture
|
|
||||||
- Scale the image to a reasonable size
|
|
||||||
- Upload to a public readable place - restrict size, catch all errors, multiple file types
|
|
||||||
- check the rate limits
|
|
||||||
- use CID (hash?)
|
|
||||||
- put the image URL in the claim
|
|
||||||
- Rates - images erased?
|
|
||||||
- image not associated with JWT ULID since that's assigned later
|
|
||||||
|
|
||||||
- 24 make the contact browsing on the front page something that invites more action
|
- 24 make the contact browsing on the front page something that invites more action
|
||||||
|
|
||||||
- .2 list the "show more" contacts alphabetically
|
|
||||||
- .5 change server plan & project endpoints to use jwtId as identifier rather than rowid
|
- .5 change server plan & project endpoints to use jwtId as identifier rather than rowid
|
||||||
- 16 edit offers & gives, or revoke allowing re-creation
|
- 16 edit offers & gives, or revoke allowing re-creation
|
||||||
- .1 When available in the server, give message for 'nonAmountGiven' on offers on ProjectsView page.
|
- .1 When available in the server, give message for 'nonAmountGiven' on offers on ProjectsView page.
|
||||||
- .1 Add help instructions for "Encryption key has changed" error. (It is a problem if localStorage is cleared, but the contacts & settings remain and they have to restore their seeds.)
|
- .1 Add help instructions for "Encryption key has changed" error. (It is a problem if localStorage is cleared, but the contacts & settings remain and they have to restore their seeds.)
|
||||||
- .5 add more detail on TimeSafari.org
|
|
||||||
- .1 show better error when user with no ID goes to the "My Project" page
|
- .1 show better error when user with no ID goes to the "My Project" page
|
||||||
- 08 add button to front page to prompt for ideas for gratitude :
|
- 01 in front page prompt for ideas for gratitude :
|
||||||
- show previous on "Your" screen
|
- randomize (not show in order)
|
||||||
- checkboxes - randomize vs show in order, show non-person-oriented messages, show only contacts, show only projects
|
- checkboxes - show non-person-oriented messages, show only contacts, show only projects
|
||||||
|
|
||||||
- .5 add a notice on the front page if their notifications are off
|
- .5 add a notice on the front page if their notifications are off
|
||||||
- 08 allow user to add a time when they want their daily notification
|
- 08 allow user to add a time when they want their daily notification
|
||||||
@@ -80,6 +77,7 @@ tasks :
|
|||||||
- create a help-desk document & add screenshots
|
- create a help-desk document & add screenshots
|
||||||
|
|
||||||
- .1 update "offer" units to have same functionality as "give" units
|
- .1 update "offer" units to have same functionality as "give" units
|
||||||
|
- .5 add a link to any 'give' records that fulfill an offer on ClaimView
|
||||||
- 01 on home page, prompt for install check in addition to "supports notifications" check (since they won't get notified if Chrome is closed)
|
- 01 on home page, prompt for install check in addition to "supports notifications" check (since they won't get notified if Chrome is closed)
|
||||||
- 01 on Mac (& Windows?) desktop, add a help blurb so that they can find it again (since it doesn't show in Application list)
|
- 01 on Mac (& Windows?) desktop, add a help blurb so that they can find it again (since it doesn't show in Application list)
|
||||||
- bug (that is hard to reproduce) - got error adding on Firefox user #0 as contact for themselves
|
- bug (that is hard to reproduce) - got error adding on Firefox user #0 as contact for themselves
|
||||||
|
|||||||
15
src/App.vue
15
src/App.vue
@@ -288,21 +288,14 @@ interface VapidResponse {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
|
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
|
||||||
import { db } from "@/db/index";
|
import { db } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { sendTestThroughPushServer } from "@/libs/util";
|
import { sendTestThroughPushServer } from "@/libs/util";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class App extends Vue {
|
export default class App extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
b64 = "";
|
b64 = "";
|
||||||
serviceWorkerReady = false;
|
serviceWorkerReady = false;
|
||||||
@@ -589,7 +582,7 @@ export default class App extends Vue {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log("Push provider server communication failed:", error);
|
console.error("Push provider server communication failed:", error);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -604,7 +597,7 @@ export default class App extends Vue {
|
|||||||
return response.ok;
|
return response.ok;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log("Push server communication failed:", error);
|
console.error("Push server communication failed:", error);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,8 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import {
|
import {
|
||||||
createAndSubmitGive,
|
createAndSubmitGive,
|
||||||
didInfo,
|
didInfo,
|
||||||
@@ -83,19 +85,11 @@ import {
|
|||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
import { Account } from "@/db/tables/accounts";
|
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class GiftedDialog extends Vue {
|
export default class GiftedDialog extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
@Prop message = "";
|
@Prop message = "";
|
||||||
@Prop projectId = "";
|
@Prop projectId = "";
|
||||||
@@ -219,22 +213,6 @@ export default class GiftedDialog extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
|
||||||
await accountsDB.open();
|
|
||||||
const account = (await accountsDB.accounts
|
|
||||||
.where("did")
|
|
||||||
.equals(activeDid)
|
|
||||||
.first()) as Account;
|
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
|
||||||
|
|
||||||
if (!identity) {
|
|
||||||
throw new Error(
|
|
||||||
"Attempted to load Give records for DID ${activeDid} but no identifier was found",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param giverDid may be null
|
* @param giverDid may be null
|
||||||
@@ -275,7 +253,7 @@ export default class GiftedDialog extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
const identity = await libsUtil.getIdentity(this.activeDid);
|
||||||
const result = await createAndSubmitGive(
|
const result = await createAndSubmitGive(
|
||||||
this.axios,
|
this.axios,
|
||||||
this.apiServer,
|
this.apiServer,
|
||||||
|
|||||||
@@ -64,20 +64,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component } from "vue-facing-decorator";
|
import { Vue, Component } from "vue-facing-decorator";
|
||||||
|
|
||||||
import { AppString } from "@/constants/app";
|
import { AppString, NotificationIface } from "@/constants/app";
|
||||||
import { db } from "@/db/index";
|
import { db } from "@/db/index";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class GivenPrompts extends Vue {
|
export default class GivenPrompts extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
IDEAS = [
|
IDEAS = [
|
||||||
"Did anyone fix food for you?",
|
"Did anyone fix food for you?",
|
||||||
|
|||||||
@@ -68,22 +68,16 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { createAndSubmitOffer } from "@/libs/endorserServer";
|
import { createAndSubmitOffer } from "@/libs/endorserServer";
|
||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { db } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
import { Account } from "@/db/tables/accounts";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class OfferDialog extends Vue {
|
export default class OfferDialog extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
@Prop message = "";
|
@Prop message = "";
|
||||||
@Prop projectId = "";
|
@Prop projectId = "";
|
||||||
@@ -107,7 +101,7 @@ export default class OfferDialog extends Vue {
|
|||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log("Error retrieving settings from database:", err);
|
console.error("Error retrieving settings from database:", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -178,22 +172,6 @@ export default class OfferDialog extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
|
||||||
await accountsDB.open();
|
|
||||||
const account = (await accountsDB.accounts
|
|
||||||
.where("did")
|
|
||||||
.equals(activeDid)
|
|
||||||
.first()) as Account;
|
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
|
||||||
|
|
||||||
if (!identity) {
|
|
||||||
throw new Error(
|
|
||||||
`Attempted to load Offer records for DID ${activeDid} but no identifier was found`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param description may be an empty string
|
* @param description may be an empty string
|
||||||
@@ -233,7 +211,7 @@ export default class OfferDialog extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
const identity = await libsUtil.getIdentity(this.activeDid);
|
||||||
const result = await createAndSubmitOffer(
|
const result = await createAndSubmitOffer(
|
||||||
this.axios,
|
this.axios,
|
||||||
this.apiServer,
|
this.apiServer,
|
||||||
@@ -250,7 +228,7 @@ export default class OfferDialog extends Vue {
|
|||||||
this.isOfferCreationError(result.response)
|
this.isOfferCreationError(result.response)
|
||||||
) {
|
) {
|
||||||
const errorMessage = this.getOfferCreationErrorMessage(result);
|
const errorMessage = this.getOfferCreationErrorMessage(result);
|
||||||
console.log("Error with offer creation result:", result);
|
console.error("Error with offer creation result:", result);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -273,7 +251,7 @@ export default class OfferDialog extends Vue {
|
|||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log("Error with offer recordation caught:", error);
|
console.error("Error with offer recordation caught:", error);
|
||||||
const message =
|
const message =
|
||||||
error.userMessage ||
|
error.userMessage ||
|
||||||
error.response?.data?.error?.message ||
|
error.response?.data?.error?.message ||
|
||||||
|
|||||||
@@ -4,20 +4,14 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import { AppString, NotificationIface } from "@/constants/app";
|
||||||
import { db } from "@/db/index";
|
import { db } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { AppString } from "@/constants/app";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class TopMessage extends Vue {
|
export default class TopMessage extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
@Prop selected = "";
|
@Prop selected = "";
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,13 @@ export enum AppString {
|
|||||||
NO_CONTACT_NAME = "(no name)",
|
NO_CONTACT_NAME = "(no name)",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_ENDORSER_API_SERVER = AppString.TEST_ENDORSER_API_SERVER;
|
export const DEFAULT_ENDORSER_API_SERVER =
|
||||||
export const DEFAULT_IMAGE_API_SERVER = AppString.LOCAL_IMAGE_API_SERVER;
|
process.env.VUE_APP_DEFAULT_ENDORSER_API_SERVER ||
|
||||||
|
AppString.TEST_ENDORSER_API_SERVER;
|
||||||
|
|
||||||
|
export const DEFAULT_IMAGE_API_SERVER =
|
||||||
|
process.env.VUE_APP_DEFAULT_IMAGE_API_SERVER ||
|
||||||
|
AppString.TEST_IMAGE_API_SERVER;
|
||||||
|
|
||||||
export const DEFAULT_PUSH_SERVER =
|
export const DEFAULT_PUSH_SERVER =
|
||||||
window.location.protocol + "//" + window.location.host;
|
window.location.protocol + "//" + window.location.host;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export type Settings = {
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
showContactGivesInline?: boolean; // Display contact inline or not
|
showContactGivesInline?: boolean; // Display contact inline or not
|
||||||
|
showShortcutBvc?: boolean; // Show shortcut for BVC actions
|
||||||
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
|
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
|
||||||
warnIfProdServer?: boolean; // Warn if using a production server
|
warnIfProdServer?: boolean; // Warn if using a production server
|
||||||
warnIfTestServer?: boolean; // Warn if using a testing server
|
warnIfTestServer?: boolean; // Warn if using a testing server
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export interface AgreeVerifiableCredential {
|
|||||||
"@type": string;
|
"@type": string;
|
||||||
// "any" because arbitrary objects can be subject of agreement
|
// "any" because arbitrary objects can be subject of agreement
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
object: Record<any, any>;
|
object: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GiverInputInfo {
|
export interface GiverInputInfo {
|
||||||
@@ -46,21 +46,25 @@ export interface ClaimResult {
|
|||||||
export interface GenericVerifiableCredential {
|
export interface GenericVerifiableCredential {
|
||||||
"@context": string;
|
"@context": string;
|
||||||
"@type": string;
|
"@type": string;
|
||||||
|
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericServerRecord extends GenericVerifiableCredential {
|
export interface GenericServerRecord extends GenericVerifiableCredential {
|
||||||
handleId?: string;
|
handleId?: string;
|
||||||
id?: string;
|
id: string;
|
||||||
issuedAt?: string;
|
issuedAt: string;
|
||||||
issuer?: string;
|
issuer: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
claim: Record<any, any>;
|
claim: Record<string, any>;
|
||||||
claimType?: string;
|
claimType?: string;
|
||||||
}
|
}
|
||||||
export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = {
|
export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = {
|
||||||
"@context": SCHEMA_ORG_CONTEXT,
|
"@context": SCHEMA_ORG_CONTEXT,
|
||||||
"@type": "",
|
"@type": "",
|
||||||
claim: {},
|
claim: {},
|
||||||
|
id: "",
|
||||||
|
issuedAt: "",
|
||||||
|
issuer: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface GiveServerRecord {
|
export interface GiveServerRecord {
|
||||||
@@ -226,16 +230,16 @@ export interface ErrorResponse {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ErrorResult {
|
|
||||||
type: "error";
|
|
||||||
error: InternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InternalError {
|
export interface InternalError {
|
||||||
error: string; // for system logging
|
error: string; // for system logging
|
||||||
userMessage?: string; // for user display
|
userMessage?: string; // for user display
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ErrorResult extends ResultWithType {
|
||||||
|
type: "error";
|
||||||
|
error: InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
export type CreateAndSubmitClaimResult = SuccessResult | ErrorResult;
|
export type CreateAndSubmitClaimResult = SuccessResult | ErrorResult;
|
||||||
|
|
||||||
// This is used to check for hidden info.
|
// This is used to check for hidden info.
|
||||||
@@ -327,7 +331,7 @@ export function addLastClaimOrHandleAsIdIfMissing(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return clone of object without any nested *VisibleToDids keys
|
// return clone of object without any nested *VisibleToDids keys
|
||||||
// similar logic is found in endorser-mobile
|
// similar code is also contained in endorser-mobile
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function removeVisibleToDids(input: any): any {
|
export function removeVisibleToDids(input: any): any {
|
||||||
if (input instanceof Object) {
|
if (input instanceof Object) {
|
||||||
@@ -337,7 +341,6 @@ export function removeVisibleToDids(input: any): any {
|
|||||||
const result: Record<string, any> = {};
|
const result: Record<string, any> = {};
|
||||||
for (const key in input) {
|
for (const key in input) {
|
||||||
if (!key.endsWith("VisibleToDids")) {
|
if (!key.endsWith("VisibleToDids")) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
result[key] = removeVisibleToDids(R.clone(input[key]));
|
result[key] = removeVisibleToDids(R.clone(input[key]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,7 +349,6 @@ export function removeVisibleToDids(input: any): any {
|
|||||||
// it's an array
|
// it's an array
|
||||||
return R.map(removeVisibleToDids, input);
|
return R.map(removeVisibleToDids, input);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
@@ -515,6 +517,28 @@ export async function createAndSubmitOffer(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// similar logic is found in endorser-mobile
|
||||||
|
export const createAndSubmitConfirmation = async (
|
||||||
|
identifier: IIdentifier,
|
||||||
|
claim: GenericVerifiableCredential,
|
||||||
|
lastClaimId: string, // used to set the lastClaimId
|
||||||
|
handleId: string | undefined,
|
||||||
|
apiServer: string,
|
||||||
|
axios: Axios,
|
||||||
|
) => {
|
||||||
|
const goodClaim = removeSchemaContext(
|
||||||
|
removeVisibleToDids(
|
||||||
|
addLastClaimOrHandleAsIdIfMissing(claim, lastClaimId, handleId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const confirmationClaim: GenericVerifiableCredential = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "AgreeAction",
|
||||||
|
object: goodClaim,
|
||||||
|
};
|
||||||
|
return createAndSubmitClaim(confirmationClaim, identifier, apiServer, axios);
|
||||||
|
};
|
||||||
|
|
||||||
export async function createAndSubmitClaim(
|
export async function createAndSubmitClaim(
|
||||||
vcClaim: GenericVerifiableCredential,
|
vcClaim: GenericVerifiableCredential,
|
||||||
identity: IIdentifier,
|
identity: IIdentifier,
|
||||||
@@ -579,12 +603,199 @@ export async function createAndSubmitClaim(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// from https://stackoverflow.com/a/175787/845494
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
//
|
export const isAccept = (claim: Record<string, any>) => {
|
||||||
export function isNumeric(str: string): boolean {
|
return (
|
||||||
return !isNaN(+str);
|
claim &&
|
||||||
|
claim["@context"] === SCHEMA_ORG_CONTEXT &&
|
||||||
|
claim["@type"] === "AcceptAction"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isOffer = (claim: Record<string, any>) => {
|
||||||
|
return (
|
||||||
|
claim &&
|
||||||
|
claim["@context"] === SCHEMA_ORG_CONTEXT &&
|
||||||
|
claim["@type"] === "Offer"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function currencyShortWordForCode(unitCode: string, single: boolean) {
|
||||||
|
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function numberOrZero(str: string): number {
|
export function displayAmount(code: string, amt: number) {
|
||||||
return isNumeric(str) ? +str : 0;
|
return "" + amt + " " + currencyShortWordForCode(code, amt === 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// insert a space before any capital letters except the initial letter
|
||||||
|
// (and capitalize initial letter, just in case)
|
||||||
|
export const capitalizeAndInsertSpacesBeforeCaps = (text: string) => {
|
||||||
|
return !text
|
||||||
|
? ""
|
||||||
|
: text[0].toUpperCase() + text.substr(1).replace(/([A-Z])/g, " $1");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
return readable summary of claim, or something generic
|
||||||
|
|
||||||
|
similar code is also contained in endorser-mobile
|
||||||
|
**/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const claimSummary = (claim: Record<string, any>) => {
|
||||||
|
if (!claim) {
|
||||||
|
// to differentiate from "something" above
|
||||||
|
return "something";
|
||||||
|
}
|
||||||
|
if (claim.claim) {
|
||||||
|
// probably a Verified Credential
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
claim = claim.claim as Record<string, any>;
|
||||||
|
}
|
||||||
|
if (Array.isArray(claim)) {
|
||||||
|
if (claim.length === 1) {
|
||||||
|
claim = claim[0];
|
||||||
|
} else {
|
||||||
|
return "multiple claims";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const type = claim["@type"];
|
||||||
|
if (!type) {
|
||||||
|
return "a claim";
|
||||||
|
} else {
|
||||||
|
let typeExpl = capitalizeAndInsertSpacesBeforeCaps(type);
|
||||||
|
if (typeExpl === "Person") {
|
||||||
|
typeExpl += " claim";
|
||||||
|
}
|
||||||
|
return "a " + typeExpl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
return readable description of claim if possible, as a past-tense action
|
||||||
|
|
||||||
|
identifiers is a list of objects with a 'did' field, each representing the user
|
||||||
|
contacts is a list of objects with a 'did' field for others and a 'name' field for their name
|
||||||
|
|
||||||
|
similar code is also contained in endorser-mobile
|
||||||
|
**/
|
||||||
|
export const claimSpecialDescription = (
|
||||||
|
record: GenericServerRecord,
|
||||||
|
activeDid: string,
|
||||||
|
identifiers: Array<string>,
|
||||||
|
contacts: Array<Contact>,
|
||||||
|
) => {
|
||||||
|
let claim = record.claim;
|
||||||
|
if (claim.claim) {
|
||||||
|
// it's probably a Verified Credential
|
||||||
|
claim = claim.claim;
|
||||||
|
}
|
||||||
|
|
||||||
|
const issuer = didInfo(record.issuer, activeDid, identifiers, contacts);
|
||||||
|
const type = claim["@type"] || "UnknownType";
|
||||||
|
|
||||||
|
if (type === "AgreeAction") {
|
||||||
|
return issuer + " agreed with " + claimSummary(claim.object);
|
||||||
|
} else if (isAccept(claim)) {
|
||||||
|
return issuer + " accepted " + claimSummary(claim.object);
|
||||||
|
} else if (type === "GiveAction") {
|
||||||
|
// agent.did is for legacy data, before March 2023
|
||||||
|
const giver = claim.agent?.identifier || claim.agent?.did;
|
||||||
|
const giverInfo = didInfo(giver, activeDid, identifiers, contacts);
|
||||||
|
let gaveAmount = claim.object?.amountOfThisGood
|
||||||
|
? displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
|
||||||
|
: "";
|
||||||
|
if (claim.description) {
|
||||||
|
if (gaveAmount) {
|
||||||
|
gaveAmount = gaveAmount + ", and also: ";
|
||||||
|
}
|
||||||
|
gaveAmount = gaveAmount + claim.description;
|
||||||
|
}
|
||||||
|
if (!gaveAmount) {
|
||||||
|
gaveAmount = "something not described";
|
||||||
|
}
|
||||||
|
// recipient.did is for legacy data, before March 2023
|
||||||
|
const gaveRecipientId = claim.recipient?.identifier || claim.recipient?.did;
|
||||||
|
const gaveRecipientInfo = gaveRecipientId
|
||||||
|
? " to " + didInfo(gaveRecipientId, activeDid, identifiers, contacts)
|
||||||
|
: "";
|
||||||
|
return giverInfo + " gave" + gaveRecipientInfo + ": " + gaveAmount;
|
||||||
|
} else if (type === "JoinAction") {
|
||||||
|
// agent.did is for legacy data, before March 2023
|
||||||
|
const agent = claim.agent?.identifier || claim.agent?.did;
|
||||||
|
const contactInfo = didInfo(agent, activeDid, identifiers, contacts);
|
||||||
|
|
||||||
|
let eventOrganizer =
|
||||||
|
claim.event && claim.event.organizer && claim.event.organizer.name;
|
||||||
|
eventOrganizer = eventOrganizer || "";
|
||||||
|
let eventName = claim.event && claim.event.name;
|
||||||
|
eventName = eventName ? " " + eventName : "";
|
||||||
|
let fullEvent = eventOrganizer + eventName;
|
||||||
|
fullEvent = fullEvent ? " attended the " + fullEvent : "";
|
||||||
|
|
||||||
|
let eventDate = claim.event && claim.event.startTime;
|
||||||
|
eventDate = eventDate ? " at " + eventDate : "";
|
||||||
|
return contactInfo + fullEvent + eventDate;
|
||||||
|
} else if (isOffer(claim)) {
|
||||||
|
const offerer = claim.offeredBy?.identifier;
|
||||||
|
const contactInfo = didInfo(offerer, activeDid, identifiers, contacts);
|
||||||
|
let offering = "";
|
||||||
|
if (claim.includesObject) {
|
||||||
|
offering +=
|
||||||
|
" " +
|
||||||
|
displayAmount(
|
||||||
|
claim.includesObject.unitCode,
|
||||||
|
claim.includesObject.amountOfThisGood,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (claim.itemOffered?.description) {
|
||||||
|
offering += ", saying: " + claim.itemOffered?.description;
|
||||||
|
}
|
||||||
|
// recipient.did is for legacy data, before March 2023
|
||||||
|
const offerRecipientId =
|
||||||
|
claim.recipient?.identifier || claim.recipient?.did;
|
||||||
|
const offerRecipientInfo = offerRecipientId
|
||||||
|
? " to " + didInfo(offerRecipientId, activeDid, identifiers, contacts)
|
||||||
|
: "";
|
||||||
|
return contactInfo + " offered" + offering + offerRecipientInfo;
|
||||||
|
} else if (type === "PlanAction") {
|
||||||
|
const claimer = claim.agent?.identifier || record.issuer;
|
||||||
|
const claimerInfo = didInfo(claimer, activeDid, identifiers, contacts);
|
||||||
|
return claimerInfo + " announced a project: " + claim.name;
|
||||||
|
} else if (type === "Tenure") {
|
||||||
|
// party.did is for legacy data, before March 2023
|
||||||
|
const claimer = claim.party?.identifier || claim.party?.did;
|
||||||
|
const contactInfo = didInfo(claimer, activeDid, identifiers, contacts);
|
||||||
|
const polygon = claim.spatialUnit?.geo?.polygon || "";
|
||||||
|
return (
|
||||||
|
contactInfo +
|
||||||
|
" possesses [" +
|
||||||
|
polygon.substring(0, polygon.indexOf(" ")) +
|
||||||
|
"...]"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return issuer + " declared " + claimSummary(claim as GenericServerRecord);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BVC_MEETUPS_PROJECT_CLAIM_ID =
|
||||||
|
process.env.VUE_APP_BVC_MEETUPS_PROJECT_CLAIM_ID ||
|
||||||
|
"https://endorser.ch/entity/01HNTZYJJXTGT0EZS3VEJGX7AK"; // this won't resolve as a URL on production; it's a URN only found in the test system
|
||||||
|
|
||||||
|
export const bvcMeetingJoinClaim = (did: string, startTime: string) => {
|
||||||
|
return {
|
||||||
|
"@context": SCHEMA_ORG_CONTEXT,
|
||||||
|
"@type": "JoinAction",
|
||||||
|
agent: {
|
||||||
|
identifier: did,
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
organizer: {
|
||||||
|
name: "Bountiful Voluntaryist Community",
|
||||||
|
},
|
||||||
|
name: "Saturday Morning Meeting",
|
||||||
|
startTime: startTime,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
// many of these are also found in endorser-mobile utility.ts
|
// many of these are also found in endorser-mobile utility.ts
|
||||||
|
|
||||||
import axios, { AxiosResponse } from "axios";
|
import axios, { AxiosResponse } from "axios";
|
||||||
|
import { IIdentifier } from "@veramo/core";
|
||||||
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
|
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
|
import { Account } from "@/db/tables/accounts";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
|
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
|
||||||
import { GenericServerRecord, containsHiddenDid } from "@/libs/endorserServer";
|
import { GenericServerRecord, containsHiddenDid } from "@/libs/endorserServer";
|
||||||
import * as serverUtil from "@/libs/endorserServer";
|
import * as serverUtil from "@/libs/endorserServer";
|
||||||
import { useClipboard } from "@vueuse/core";
|
|
||||||
|
|
||||||
// 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;
|
||||||
@@ -55,6 +57,16 @@ export function iconForUnitCode(unitCode: string) {
|
|||||||
return UNIT_CODES[unitCode]?.faIcon || "question";
|
return UNIT_CODES[unitCode]?.faIcon || "question";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from https://stackoverflow.com/a/175787/845494
|
||||||
|
//
|
||||||
|
export function isNumeric(str: string): boolean {
|
||||||
|
return !isNaN(+str);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function numberOrZero(str: string): number {
|
||||||
|
return isNumeric(str) ? +str : 0;
|
||||||
|
}
|
||||||
|
|
||||||
export const isGlobalUri = (uri: string) => {
|
export const isGlobalUri = (uri: string) => {
|
||||||
return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/));
|
return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/));
|
||||||
};
|
};
|
||||||
@@ -180,6 +192,22 @@ export function findAllVisibleToDids(
|
|||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
export const getIdentity = async (activeDid: string): Promise<IIdentifier> => {
|
||||||
|
await accountsDB.open();
|
||||||
|
const account = (await accountsDB.accounts
|
||||||
|
.where("did")
|
||||||
|
.equals(activeDid)
|
||||||
|
.first()) as Account;
|
||||||
|
const identity = JSON.parse(account?.identity || "null");
|
||||||
|
|
||||||
|
if (!identity) {
|
||||||
|
throw new Error(
|
||||||
|
`Attempted to load Offer records for DID ${activeDid} but no identifier was found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return identity;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new identity, saves it to the database, and sets it as the active identity.
|
* Generates a new identity, saves it to the database, and sets it as the active identity.
|
||||||
* @return {Promise<string>} with the DID of the new identity
|
* @return {Promise<string>} with the DID of the new identity
|
||||||
|
|||||||
@@ -171,6 +171,30 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
import(/* webpackChunkName: "projects" */ "../views/ProjectsView.vue"),
|
import(/* webpackChunkName: "projects" */ "../views/ProjectsView.vue"),
|
||||||
beforeEnter: enterOrStart,
|
beforeEnter: enterOrStart,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/quick-action-bvc",
|
||||||
|
name: "quick-action-bvc",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "quick-action-bvc" */ "../views/QuickActionBvcView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/quick-action-bvc-begin",
|
||||||
|
name: "quick-action-bvc-begin",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "quick-action-bvc-begin" */ "../views/QuickActionBvcBeginView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/quick-action-bvc-end",
|
||||||
|
name: "quick-action-bvc-end",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "quick-action-bvc-end" */ "../views/QuickActionBvcEndView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/scan-contact",
|
path: "/scan-contact",
|
||||||
name: "scan-contact",
|
name: "scan-contact",
|
||||||
|
|||||||
@@ -299,7 +299,7 @@
|
|||||||
<label
|
<label
|
||||||
for="toggleShowAmounts"
|
for="toggleShowAmounts"
|
||||||
class="flex items-center justify-between cursor-pointer my-4"
|
class="flex items-center justify-between cursor-pointer my-4"
|
||||||
@click="handleChange"
|
@click="toggleShowContactAmounts"
|
||||||
>
|
>
|
||||||
<!-- label -->
|
<!-- label -->
|
||||||
<span class="text-slate-500 text-sm font-bold">Contacts Display</span>
|
<span class="text-slate-500 text-sm font-bold">Contacts Display</span>
|
||||||
@@ -439,6 +439,28 @@
|
|||||||
{{ DEFAULT_PUSH_SERVER }}
|
{{ DEFAULT_PUSH_SERVER }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<label
|
||||||
|
for="toggleShowShortcutBvc"
|
||||||
|
class="flex items-center justify-between cursor-pointer my-4"
|
||||||
|
@click="toggleShowShortcutBvc"
|
||||||
|
>
|
||||||
|
<!-- label -->
|
||||||
|
<span class="text-slate-500 text-sm font-bold"
|
||||||
|
>Show BVC Shortcut on Home Page</span
|
||||||
|
>
|
||||||
|
<!-- toggle -->
|
||||||
|
<div class="relative ml-2">
|
||||||
|
<!-- input -->
|
||||||
|
<input type="checkbox" v-model="showShortcutBvc" class="sr-only" />
|
||||||
|
<!-- line -->
|
||||||
|
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
||||||
|
<!-- dot -->
|
||||||
|
<div
|
||||||
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<h2 class="text-slate-500 text-sm font-bold">
|
<h2 class="text-slate-500 text-sm font-bold">
|
||||||
Contacts & Settings Database
|
Contacts & Settings Database
|
||||||
@@ -485,7 +507,11 @@ import { useClipboard } from "@vueuse/core";
|
|||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
import { AppString, DEFAULT_PUSH_SERVER } from "@/constants/app";
|
import {
|
||||||
|
AppString,
|
||||||
|
DEFAULT_PUSH_SERVER,
|
||||||
|
NotificationIface,
|
||||||
|
} from "@/constants/app";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
@@ -495,13 +521,6 @@ import { ErrorResponse, RateLimits } 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;
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IAccount {
|
interface IAccount {
|
||||||
did: string;
|
did: string;
|
||||||
publicKeyHex: string;
|
publicKeyHex: string;
|
||||||
@@ -513,7 +532,7 @@ const inputFileNameRef = ref<Blob>();
|
|||||||
|
|
||||||
@Component({ components: { QuickNav, TopMessage } })
|
@Component({ components: { QuickNav, TopMessage } })
|
||||||
export default class AccountViewView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
AppConstants = AppString;
|
AppConstants = AppString;
|
||||||
DEFAULT_PUSH_SERVER = DEFAULT_PUSH_SERVER;
|
DEFAULT_PUSH_SERVER = DEFAULT_PUSH_SERVER;
|
||||||
@@ -540,6 +559,7 @@ export default class AccountViewView extends Vue {
|
|||||||
showB64Copy = false;
|
showB64Copy = false;
|
||||||
showPubCopy = false;
|
showPubCopy = false;
|
||||||
showAdvanced = false;
|
showAdvanced = false;
|
||||||
|
showShortcutBvc = false;
|
||||||
subscription: PushSubscription | null = null;
|
subscription: PushSubscription | null = null;
|
||||||
warnIfProdServer = false;
|
warnIfProdServer = false;
|
||||||
warnIfTestServer = false;
|
warnIfTestServer = false;
|
||||||
@@ -599,6 +619,7 @@ export default class AccountViewView extends Vue {
|
|||||||
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
|
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
this.showContactGives = !!settings?.showContactGivesInline;
|
this.showContactGives = !!settings?.showContactGivesInline;
|
||||||
|
this.showShortcutBvc = !!settings?.showShortcutBvc;
|
||||||
this.warnIfProdServer = !!settings?.warnIfProdServer;
|
this.warnIfProdServer = !!settings?.warnIfProdServer;
|
||||||
this.warnIfTestServer = !!settings?.warnIfTestServer;
|
this.warnIfTestServer = !!settings?.warnIfTestServer;
|
||||||
this.webPushServer = (settings?.webPushServer as string) || "";
|
this.webPushServer = (settings?.webPushServer as string) || "";
|
||||||
@@ -656,7 +677,7 @@ export default class AccountViewView extends Vue {
|
|||||||
.then(() => setTimeout(fn, 2000));
|
.then(() => setTimeout(fn, 2000));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange() {
|
toggleShowContactAmounts() {
|
||||||
this.showContactGives = !this.showContactGives;
|
this.showContactGives = !this.showContactGives;
|
||||||
this.updateShowContactAmounts();
|
this.updateShowContactAmounts();
|
||||||
}
|
}
|
||||||
@@ -671,6 +692,11 @@ export default class AccountViewView extends Vue {
|
|||||||
this.updateWarnIfTestServer(this.warnIfTestServer);
|
this.updateWarnIfTestServer(this.warnIfTestServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleShowShortcutBvc() {
|
||||||
|
this.showShortcutBvc = !this.showShortcutBvc;
|
||||||
|
this.updateShowShortcutBvc(this.showShortcutBvc);
|
||||||
|
}
|
||||||
|
|
||||||
readableTime(timeStr: string) {
|
readableTime(timeStr: string) {
|
||||||
return timeStr.substring(0, timeStr.indexOf("T"));
|
return timeStr.substring(0, timeStr.indexOf("T"));
|
||||||
}
|
}
|
||||||
@@ -766,7 +792,7 @@ export default class AccountViewView extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
console.error(
|
console.error(
|
||||||
"Telling user to try again after contact setting update because:",
|
"Telling user to try again after contact-amounts setting update because:",
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -789,7 +815,7 @@ export default class AccountViewView extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
console.error(
|
console.error(
|
||||||
"Telling user to try again after setting update because:",
|
"Telling user to try again after prod-server-warning setting update because:",
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -812,7 +838,30 @@ export default class AccountViewView extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
console.error(
|
console.error(
|
||||||
"Telling user to try again after setting update because:",
|
"Telling user to try again after test-server-warning setting update because:",
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateShowShortcutBvc(newSetting: boolean) {
|
||||||
|
try {
|
||||||
|
await db.open();
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
showShortcutBvc: newSetting,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error Updating BVC Shortcut Setting",
|
||||||
|
text: "The setting may not have saved. Try again, maybe after restarting the app.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
"Telling user to try again after BVC-shortcut setting update because:",
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -407,6 +407,7 @@ import { useClipboard } from "@vueuse/core";
|
|||||||
|
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import OfferDialog from "@/components/OfferDialog.vue";
|
import OfferDialog from "@/components/OfferDialog.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
@@ -418,18 +419,11 @@ import EntityIcon from "@/components/EntityIcon.vue";
|
|||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
import { GiverInputInfo } from "@/libs/endorserServer";
|
import { GiverInputInfo } from "@/libs/endorserServer";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav },
|
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav },
|
||||||
})
|
})
|
||||||
export default class ClaimView extends Vue {
|
export default class ClaimView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
accountIdentityStr: string = "null";
|
accountIdentityStr: string = "null";
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
@@ -736,10 +730,7 @@ export default class ClaimView extends Vue {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const confirmationClaim: serverUtil.GenericVerifiableCredential & {
|
const confirmationClaim: serverUtil.GenericVerifiableCredential = {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
object: any;
|
|
||||||
} = {
|
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "AgreeAction",
|
"@type": "AgreeAction",
|
||||||
object: goodClaim,
|
object: goodClaim,
|
||||||
|
|||||||
@@ -105,9 +105,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import * as didJwt from "did-jwt";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
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";
|
||||||
@@ -118,21 +123,10 @@ import {
|
|||||||
GiveVerifiableCredential,
|
GiveVerifiableCredential,
|
||||||
SCHEMA_ORG_CONTEXT,
|
SCHEMA_ORG_CONTEXT,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import * as didJwt from "did-jwt";
|
|
||||||
import { AxiosError } from "axios";
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
|
||||||
import { IIdentifier } from "@veramo/core";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class ContactAmountssView extends Vue {
|
export default class ContactAmountssView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
@@ -185,7 +179,7 @@ export default class ContactAmountssView extends Vue {
|
|||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log("Error retrieving settings or gives.", err);
|
console.error("Error retrieving settings or gives.", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
|
|||||||
@@ -76,29 +76,24 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
import { IIdentifier } from "@veramo/core";
|
||||||
|
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db/index";
|
||||||
import { Account, AccountsSchema } from "@/db/tables/accounts";
|
import { Account, AccountsSchema } from "@/db/tables/accounts";
|
||||||
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import { GiverInputInfo } from "@/libs/endorserServer";
|
import { GiverInputInfo } from "@/libs/endorserServer";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
|
||||||
import { IIdentifier } from "@veramo/core";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { GiftedDialog, QuickNav, EntityIcon },
|
components: { GiftedDialog, QuickNav, EntityIcon },
|
||||||
})
|
})
|
||||||
export default class ContactGiftingView extends Vue {
|
export default class ContactGiftingView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
@@ -124,7 +119,7 @@ export default class ContactGiftingView extends Vue {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log("Error retrieving settings & contacts:", err);
|
console.error("Error retrieving settings & contacts:", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
import { QrcodeStream } from "vue-qrcode-reader";
|
import { QrcodeStream } from "vue-qrcode-reader";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { deriveAddress, nextDerivationPath, SimpleSigner } from "@/libs/crypto";
|
import { deriveAddress, nextDerivationPath, SimpleSigner } from "@/libs/crypto";
|
||||||
@@ -91,13 +92,6 @@ import {
|
|||||||
// 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;
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
QrcodeStream,
|
QrcodeStream,
|
||||||
@@ -106,7 +100,7 @@ interface Notification {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class ContactQRScanShow extends Vue {
|
export default class ContactQRScanShow extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
@@ -186,7 +180,6 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onScanDetect(content: any) {
|
onScanDetect(content: any) {
|
||||||
if (content[0]?.rawValue) {
|
if (content[0]?.rawValue) {
|
||||||
//console.log("onDetect", content[0].rawValue);
|
|
||||||
localStorage.setItem("contactEndorserUrl", content[0].rawValue);
|
localStorage.setItem("contactEndorserUrl", content[0].rawValue);
|
||||||
this.$router.push({ name: "contacts" });
|
this.$router.push({ name: "contacts" });
|
||||||
} else {
|
} else {
|
||||||
@@ -204,7 +197,7 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onScanError(error: any) {
|
onScanError(error: any) {
|
||||||
console.log("Scan was invalid:", error);
|
console.error("Scan was invalid:", error);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
|
|||||||
@@ -284,6 +284,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
|
import { IndexableType } from "dexie";
|
||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
@@ -311,7 +312,6 @@ import * as libsUtil from "@/libs/util";
|
|||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
import { IndexableType } from "dexie";
|
|
||||||
|
|
||||||
// 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;
|
||||||
@@ -497,7 +497,7 @@ export default class ContactsView extends Vue {
|
|||||||
this.givenToMeConfirmed = givenToMeConfirmed;
|
this.givenToMeConfirmed = givenToMeConfirmed;
|
||||||
this.givenToMeUnconfirmed = givenToMeUnconfirmed;
|
this.givenToMeUnconfirmed = givenToMeUnconfirmed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error loading gives", error);
|
console.error("Error loading gives", error);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -1000,7 +1000,7 @@ export default class ContactsView extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log("Got bad server response when checking visibility: ", resp);
|
console.error("Got bad server response checking visibility:", resp);
|
||||||
const message = resp.data.error?.message || "Got bad server response.";
|
const message = resp.data.error?.message || "Got bad server response.";
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -1013,7 +1013,7 @@ export default class ContactsView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Caught error from request to check visibility:", err);
|
console.error("Caught error from request to check visibility:", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -1026,12 +1026,6 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// from https://stackoverflow.com/a/175787/845494
|
|
||||||
//
|
|
||||||
private isNumeric(str: string): boolean {
|
|
||||||
return !isNaN(+str);
|
|
||||||
}
|
|
||||||
|
|
||||||
private nameForDid(contacts: Array<Contact>, did: string): string {
|
private nameForDid(contacts: Array<Contact>, did: string): string {
|
||||||
const contact = R.find((con) => con.did == did, contacts);
|
const contact = R.find((con) => con.did == did, contacts);
|
||||||
return this.nameForContact(contact);
|
return this.nameForContact(contact);
|
||||||
@@ -1067,7 +1061,7 @@ export default class ContactsView extends Vue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this.isNumeric(this.hourInput)) {
|
if (!libsUtil.isNumeric(this.hourInput)) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -1204,7 +1198,7 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in createAndSubmitContactGive: ", error);
|
console.error("Error in createAndSubmitContactGive: ", error);
|
||||||
let userMessage = "There was an error. See logs for more info.";
|
let userMessage = "There was an error. See logs for more info.";
|
||||||
const serverError = error as AxiosError;
|
const serverError = error as AxiosError;
|
||||||
if (serverError) {
|
if (serverError) {
|
||||||
|
|||||||
@@ -129,23 +129,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
import { accountsDB, db } from "@/db/index";
|
|
||||||
import { Contact } from "@/db/tables/contacts";
|
|
||||||
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
|
||||||
import { accessToken } from "@/libs/crypto";
|
|
||||||
import { didInfo, PlanData } from "@/libs/endorserServer";
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import ProjectIcon from "@/components/ProjectIcon.vue";
|
import ProjectIcon from "@/components/ProjectIcon.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
interface Notification {
|
import { accountsDB, db } from "@/db/index";
|
||||||
group: string;
|
import { Contact } from "@/db/tables/contacts";
|
||||||
type: string;
|
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
title: string;
|
import { accessToken } from "@/libs/crypto";
|
||||||
text: string;
|
import { didInfo, PlanData } from "@/libs/endorserServer";
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -157,7 +151,7 @@ interface Notification {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class DiscoverView extends Vue {
|
export default class DiscoverView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
@@ -260,7 +254,7 @@ export default class DiscoverView extends Vue {
|
|||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
const details = await response.text();
|
const details = await response.text();
|
||||||
console.log("Problem with full search:", details);
|
console.error("Problem with full search:", details);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -288,7 +282,7 @@ export default class DiscoverView extends Vue {
|
|||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log("Error with feed load:", e);
|
console.error("Error with feed load:", e);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -343,7 +337,7 @@ export default class DiscoverView extends Vue {
|
|||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
const details = await response.text();
|
const details = await response.text();
|
||||||
console.log("Problem with nearby search:", details);
|
console.error("Problem with nearby search:", details);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -380,7 +374,7 @@ export default class DiscoverView extends Vue {
|
|||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log("Error with feed load:", e);
|
console.error("Error with feed load:", e);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
|
|||||||
@@ -294,18 +294,12 @@
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { sendTestThroughPushServer } from "@/libs/util";
|
import { sendTestThroughPushServer } from "@/libs/util";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class HelpNotificationsView extends Vue {
|
export default class HelpNotificationsView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
subscription: PushSubscription | null = null;
|
subscription: PushSubscription | null = null;
|
||||||
|
|
||||||
|
|||||||
@@ -325,18 +325,12 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
|
|
||||||
import * as Package from "../../package.json";
|
import * as Package from "../../package.json";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { ONBOARD_MESSAGE } from "@/libs/util";
|
import { ONBOARD_MESSAGE } from "@/libs/util";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class Help extends Vue {
|
export default class Help extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
package = Package;
|
package = Package;
|
||||||
commitHash = process.env.VUE_APP_GIT_HASH;
|
commitHash = process.env.VUE_APP_GIT_HASH;
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
<TopMessage />
|
<TopMessage />
|
||||||
|
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
<section id="Content" class="p-2 pb-24 max-w-3xl mx-auto">
|
||||||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
<h1 id="ViewHeading" class="text-4xl text-center font-light px-4 mb-8">
|
||||||
Time Safari
|
Time Safari
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -59,6 +59,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showShortcutBvc" class="mb-4">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'quick-action-bvc' }"
|
||||||
|
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
|
||||||
|
>
|
||||||
|
Bountiful Voluntaryist Community Actions</router-link
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- show the actions for recognizing a give -->
|
<!-- show the actions for recognizing a give -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div v-if="isCreatingIdentifier">
|
<div v-if="isCreatingIdentifier">
|
||||||
@@ -230,6 +239,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { UAParser } from "ua-parser-js";
|
import { UAParser } from "ua-parser-js";
|
||||||
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
@@ -238,6 +248,7 @@ import GiftedPrompts from "@/components/GiftedPrompts.vue";
|
|||||||
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db/index";
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
@@ -249,16 +260,8 @@ import {
|
|||||||
GiverInputInfo,
|
GiverInputInfo,
|
||||||
GiveServerRecord,
|
GiveServerRecord,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import { IIdentifier } from "@veramo/core";
|
|
||||||
import { generateSaveAndActivateIdentity } from "@/libs/util";
|
import { generateSaveAndActivateIdentity } from "@/libs/util";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GiveRecordWithContactInfo extends GiveServerRecord {
|
interface GiveRecordWithContactInfo extends GiveServerRecord {
|
||||||
giver: {
|
giver: {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@@ -281,7 +284,7 @@ interface GiveRecordWithContactInfo extends GiveServerRecord {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class HomeView extends Vue {
|
export default class HomeView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
@@ -293,6 +296,7 @@ export default class HomeView extends Vue {
|
|||||||
isCreatingIdentifier = false;
|
isCreatingIdentifier = false;
|
||||||
isFeedLoading = true;
|
isFeedLoading = true;
|
||||||
isRegistered = false;
|
isRegistered = false;
|
||||||
|
showShortcutBvc = false;
|
||||||
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
|
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid: string) {
|
||||||
@@ -327,6 +331,7 @@ export default class HomeView extends Vue {
|
|||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await db.contacts.toArray();
|
||||||
this.feedLastViewedClaimId = settings?.lastViewedClaimId;
|
this.feedLastViewedClaimId = settings?.lastViewedClaimId;
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
this.showShortcutBvc = !!settings?.showShortcutBvc;
|
||||||
|
|
||||||
if (this.allMyDids.length === 0) {
|
if (this.allMyDids.length === 0) {
|
||||||
this.isCreatingIdentifier = true;
|
this.isCreatingIdentifier = true;
|
||||||
@@ -341,7 +346,7 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log("Error retrieving settings or feed.", err);
|
console.error("Error retrieving settings or feed.", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -447,7 +452,7 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log("Error with feed load:", e);
|
console.error("Error with feed load:", e);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -479,7 +484,7 @@ export default class HomeView extends Vue {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (!response.ok) {
|
||||||
throw await response.text();
|
throw await response.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,22 +80,16 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { AppString } from "@/constants/app";
|
|
||||||
|
import { AppString, NotificationIface } from "@/constants/app";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db/index";
|
||||||
import { AccountsSchema } from "@/db/tables/accounts";
|
import { AccountsSchema } from "@/db/tables/accounts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class IdentitySwitcherView extends Vue {
|
export default class IdentitySwitcherView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
Constants = AppString;
|
Constants = AppString;
|
||||||
public accounts: typeof AccountsSchema;
|
public accounts: typeof AccountsSchema;
|
||||||
|
|||||||
@@ -75,20 +75,15 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
|
import { accountsDB, db } from "@/db/index";
|
||||||
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import {
|
import {
|
||||||
DEFAULT_ROOT_DERIVATION_PATH,
|
DEFAULT_ROOT_DERIVATION_PATH,
|
||||||
deriveAddress,
|
deriveAddress,
|
||||||
newIdentifier,
|
newIdentifier,
|
||||||
} from "../libs/crypto";
|
} from "@/libs/crypto";
|
||||||
import { accountsDB, db } from "@/db/index";
|
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {},
|
components: {},
|
||||||
@@ -96,7 +91,7 @@ interface Notification {
|
|||||||
export default class ImportAccountView extends Vue {
|
export default class ImportAccountView extends Vue {
|
||||||
UPORT_DERIVATION_PATH = "m/7696500'/0'/0'/0'"; // for legacy imports, likely never used
|
UPORT_DERIVATION_PATH = "m/7696500'/0'/0'/0'"; // for legacy imports, likely never used
|
||||||
|
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
mnemonic = "";
|
mnemonic = "";
|
||||||
address = "";
|
address = "";
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||||
@@ -145,18 +146,11 @@ import { useAppStore } from "@/store/app";
|
|||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { PlanVerifiableCredential } from "@/libs/endorserServer";
|
import { PlanVerifiableCredential } from "@/libs/endorserServer";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { LMap, LMarker, LTileLayer, QuickNav },
|
components: { LMap, LMarker, LTileLayer, QuickNav },
|
||||||
})
|
})
|
||||||
export default class NewEditProjectView extends Vue {
|
export default class NewEditProjectView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
agentDid = "";
|
agentDid = "";
|
||||||
|
|||||||
@@ -348,7 +348,12 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import OfferDialog from "@/components/OfferDialog.vue";
|
import OfferDialog from "@/components/OfferDialog.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
|
import ProjectIcon from "@/components/ProjectIcon.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
|
import { Account } from "@/db/tables/accounts";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
@@ -362,17 +367,6 @@ import {
|
|||||||
PlanServerRecord,
|
PlanServerRecord,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import * as serverUtil from "@/libs/endorserServer";
|
import * as serverUtil from "@/libs/endorserServer";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
|
||||||
import ProjectIcon from "@/components/ProjectIcon.vue";
|
|
||||||
import { Account } from "@/db/tables/accounts";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -385,7 +379,7 @@ interface Notification {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class ProjectViewView extends Vue {
|
export default class ProjectViewView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
agentDid = "";
|
agentDid = "";
|
||||||
@@ -806,10 +800,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const confirmationClaim: serverUtil.GenericVerifiableCredential & {
|
const confirmationClaim: serverUtil.GenericVerifiableCredential = {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
object: any;
|
|
||||||
} = {
|
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "AgreeAction",
|
"@type": "AgreeAction",
|
||||||
object: goodClaim,
|
object: goodClaim,
|
||||||
|
|||||||
@@ -213,6 +213,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
@@ -225,18 +226,11 @@ import TopMessage from "@/components/TopMessage.vue";
|
|||||||
import { OfferServerRecord, PlanData } from "@/libs/endorserServer";
|
import { OfferServerRecord, PlanData } from "@/libs/endorserServer";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { EntityIcon, InfiniteScroll, QuickNav, ProjectIcon, TopMessage },
|
components: { EntityIcon, InfiniteScroll, QuickNav, ProjectIcon, TopMessage },
|
||||||
})
|
})
|
||||||
export default class ProjectsView extends Vue {
|
export default class ProjectsView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
projects: PlanData[] = [];
|
projects: PlanData[] = [];
|
||||||
|
|||||||
220
src/views/QuickActionBvcBeginView.vue
Normal file
220
src/views/QuickActionBvcBeginView.vue
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<template>
|
||||||
|
<QuickNav />
|
||||||
|
<TopMessage />
|
||||||
|
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||||
|
<!-- Back -->
|
||||||
|
<div class="text-lg text-center font-light relative px-7">
|
||||||
|
<h1
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.back()"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-left" class="fa-fw"></fa>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Heading -->
|
||||||
|
<h1 id="ViewHeading" class="text-4xl text-center font-light px-4 mb-4">
|
||||||
|
Beginning of BVC Saturday Meeting
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl m-2">You're Here</h2>
|
||||||
|
<div class="m-2 flex">
|
||||||
|
<input type="checkbox" v-model="attended" class="h-6 w-6" />
|
||||||
|
<span class="pb-2 pl-2 pr-2">Attended</span>
|
||||||
|
</div>
|
||||||
|
<div class="m-2 flex">
|
||||||
|
<input type="checkbox" v-model="gaveTime" class="h-6 w-6" />
|
||||||
|
<span class="pb-2 pl-2 pr-2">Spent Time</span>
|
||||||
|
<span v-if="gaveTime">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="How much time"
|
||||||
|
v-model="hoursStr"
|
||||||
|
size="1"
|
||||||
|
class="border border-slate-400 h-6 px-2"
|
||||||
|
/>
|
||||||
|
hour(s)
|
||||||
|
</span>
|
||||||
|
<!-- This is to match input height to avoid shifting when hiding & showing. -->
|
||||||
|
<span v-else class="h-6" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="attended || (gaveTime && hoursStr && hoursStr != '0')"
|
||||||
|
class="flex justify-center mt-4"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
@click="record()"
|
||||||
|
class="block text-center text-md font-bold bg-blue-500 text-white px-2 py-3 rounded-md w-56"
|
||||||
|
>
|
||||||
|
Sign & Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex justify-center mt-4">
|
||||||
|
<button
|
||||||
|
class="block text-center text-md font-bold bg-slate-500 text-white px-2 py-3 rounded-md w-56"
|
||||||
|
>
|
||||||
|
Select Your Actions
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import axios from "axios";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
|
import { db } from "@/db/index";
|
||||||
|
import {
|
||||||
|
BVC_MEETUPS_PROJECT_CLAIM_ID,
|
||||||
|
bvcMeetingJoinClaim,
|
||||||
|
createAndSubmitClaim,
|
||||||
|
createAndSubmitGive,
|
||||||
|
} from "@/libs/endorserServer";
|
||||||
|
import * as libsUtil from "@/libs/util";
|
||||||
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
QuickNav,
|
||||||
|
TopMessage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class QuickActionBvcBeginView extends Vue {
|
||||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
attended = true;
|
||||||
|
gaveTime = true;
|
||||||
|
hoursStr = "1";
|
||||||
|
todayOrPreviousStartDate = "";
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
let currentOrPreviousSat = DateTime.now().setZone("America/Denver");
|
||||||
|
if (currentOrPreviousSat.weekday < 6) {
|
||||||
|
// it's not Saturday or Sunday,
|
||||||
|
// so move back one week before setting to the Saturday
|
||||||
|
currentOrPreviousSat = currentOrPreviousSat.minus({ week: 1 });
|
||||||
|
}
|
||||||
|
const eventStartDateObj = currentOrPreviousSat
|
||||||
|
.set({ weekday: 6 })
|
||||||
|
.set({ hour: 9 })
|
||||||
|
.startOf("hour");
|
||||||
|
|
||||||
|
// Hack, but full ISO pushes the length to 340 which crashes verifyJWT!
|
||||||
|
this.todayOrPreviousStartDate =
|
||||||
|
eventStartDateObj.toISO({
|
||||||
|
suppressMilliseconds: true,
|
||||||
|
}) || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async record() {
|
||||||
|
await db.open();
|
||||||
|
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
||||||
|
const activeDid = settings?.activeDid || "";
|
||||||
|
const apiServer = settings?.apiServer || "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const hoursNum = libsUtil.numberOrZero(this.hoursStr);
|
||||||
|
const identity = await libsUtil.getIdentity(activeDid);
|
||||||
|
|
||||||
|
// first send the claim for time given
|
||||||
|
let timeSuccess = false;
|
||||||
|
if (this.gaveTime && hoursNum > 0) {
|
||||||
|
const timeResult = await createAndSubmitGive(
|
||||||
|
axios,
|
||||||
|
apiServer,
|
||||||
|
identity,
|
||||||
|
activeDid,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
hoursNum,
|
||||||
|
"HUR",
|
||||||
|
BVC_MEETUPS_PROJECT_CLAIM_ID,
|
||||||
|
);
|
||||||
|
if (timeResult.type === "success") {
|
||||||
|
timeSuccess = true;
|
||||||
|
} else {
|
||||||
|
console.error("Error sending time:", timeResult);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text:
|
||||||
|
timeResult?.error?.userMessage ||
|
||||||
|
"There was an error sending the time.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now send the claim for attendance
|
||||||
|
let attendedSuccess = false;
|
||||||
|
if (this.attended) {
|
||||||
|
const attendResult = await createAndSubmitClaim(
|
||||||
|
bvcMeetingJoinClaim(activeDid, this.todayOrPreviousStartDate),
|
||||||
|
identity,
|
||||||
|
apiServer,
|
||||||
|
axios,
|
||||||
|
);
|
||||||
|
if (attendResult.type === "success") {
|
||||||
|
attendedSuccess = true;
|
||||||
|
} else {
|
||||||
|
console.error("Error sending attendance:", attendResult);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text:
|
||||||
|
attendResult?.error?.userMessage ||
|
||||||
|
"There was an error sending the attendance.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeSuccess || attendedSuccess) {
|
||||||
|
const actions =
|
||||||
|
timeSuccess && attendedSuccess
|
||||||
|
? "Your attendance and time have been recorded."
|
||||||
|
: timeSuccess
|
||||||
|
? "Your time has been recorded."
|
||||||
|
: "Your attendance has been recorded.";
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Success",
|
||||||
|
text: actions,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error sending claims.", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: error.userMessage || "There was an error sending claims.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
377
src/views/QuickActionBvcEndView.vue
Normal file
377
src/views/QuickActionBvcEndView.vue
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
<template>
|
||||||
|
<QuickNav />
|
||||||
|
<TopMessage />
|
||||||
|
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||||
|
<!-- Back -->
|
||||||
|
<div class="text-lg text-center font-light relative px-7">
|
||||||
|
<h1
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.back()"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-left" class="fa-fw"></fa>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Heading -->
|
||||||
|
<h1 id="ViewHeading" class="text-4xl text-center font-light px-4 mb-4">
|
||||||
|
End of BVC Saturday Meeting
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl m-2">Confirm</h2>
|
||||||
|
<div v-if="loadingConfirms" class="flex justify-center">
|
||||||
|
<fa icon="spinner" class="animate-spin" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="claimsToConfirm.length === 0">
|
||||||
|
There are no claims yet today for you to confirm.
|
||||||
|
</div>
|
||||||
|
<ul class="border-t border-slate-300 m-2">
|
||||||
|
<li
|
||||||
|
class="border-b border-slate-300 py-2"
|
||||||
|
v-for="record in claimsToConfirm"
|
||||||
|
:key="record.id"
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-12">
|
||||||
|
<span class="col-span-11 justify-self-start">
|
||||||
|
<span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="claimsToConfirmSelected.includes(record.id)"
|
||||||
|
@click="
|
||||||
|
claimsToConfirmSelected.includes(record.id)
|
||||||
|
? claimsToConfirmSelected.splice(
|
||||||
|
claimsToConfirmSelected.indexOf(record.id),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
: claimsToConfirmSelected.push(record.id)
|
||||||
|
"
|
||||||
|
class="mr-2 h-6 w-6"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
{{
|
||||||
|
claimSpecialDescription(
|
||||||
|
record,
|
||||||
|
activeDid,
|
||||||
|
allMyDids,
|
||||||
|
allContacts,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
<a @click="onClickLoadClaim(record.id)">
|
||||||
|
<fa
|
||||||
|
icon="circle-info"
|
||||||
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div v-if="claimCountWithHidden > 0" class="border-b border-slate-300 pb-2">
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
claimCountWithHidden === 1
|
||||||
|
? "There is 1 other claim with hidden details,"
|
||||||
|
: `There are ${claimCountWithHidden} other claims with hidden details,`
|
||||||
|
}}
|
||||||
|
so if you expected but do not see details from someone then ask them to
|
||||||
|
check that their activity is visible to you on their Contacts
|
||||||
|
<fa icon="users" class="text-slate-500" />
|
||||||
|
page.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl m-2">Anything else?</h2>
|
||||||
|
<div class="m-2 flex">
|
||||||
|
<input type="checkbox" v-model="someoneGave" class="h-6 w-6" />
|
||||||
|
<span class="pb-2 pl-2 pr-2">Someone else gave</span>
|
||||||
|
<span v-if="someoneGave">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="description"
|
||||||
|
size="20"
|
||||||
|
class="border border-slate-400 h-6 px-2"
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
(Everyone likes personalized messages! 😁)
|
||||||
|
</span>
|
||||||
|
<!-- This is to match input height to avoid shifting when hiding & showing. -->
|
||||||
|
<span v-else class="h-6">...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="claimsToConfirmSelected.length || (someoneGave && description)"
|
||||||
|
class="flex justify-center mt-4"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
@click="record()"
|
||||||
|
class="block text-center text-md font-bold bg-blue-500 text-white px-2 py-3 rounded-md w-56"
|
||||||
|
>
|
||||||
|
Sign & Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex justify-center mt-4">
|
||||||
|
<button
|
||||||
|
class="block text-center text-md font-bold bg-slate-500 text-white px-2 py-3 rounded-md w-56"
|
||||||
|
>
|
||||||
|
Choose What To Confirm
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import axios from "axios";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import * as R from "ramda";
|
||||||
|
import { IIdentifier } from "@veramo/core";
|
||||||
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
|
import { accountsDB, db } from "@/db/index";
|
||||||
|
import { Account } from "@/db/tables/accounts";
|
||||||
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
|
import { accessToken } from "@/libs/crypto";
|
||||||
|
import {
|
||||||
|
BVC_MEETUPS_PROJECT_CLAIM_ID,
|
||||||
|
claimSpecialDescription,
|
||||||
|
containsHiddenDid,
|
||||||
|
createAndSubmitConfirmation,
|
||||||
|
createAndSubmitGive,
|
||||||
|
ErrorResult,
|
||||||
|
GenericServerRecord,
|
||||||
|
GenericVerifiableCredential,
|
||||||
|
} from "@/libs/endorserServer";
|
||||||
|
import * as libsUtil from "@/libs/util";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
methods: { claimSpecialDescription },
|
||||||
|
components: {
|
||||||
|
QuickNav,
|
||||||
|
TopMessage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class QuickActionBvcBeginView extends Vue {
|
||||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
activeDid = "";
|
||||||
|
allContacts: Array<Contact> = [];
|
||||||
|
allMyDids: Array<string> = [];
|
||||||
|
apiServer = "";
|
||||||
|
claimCountWithHidden = 0;
|
||||||
|
claimsToConfirm: GenericServerRecord[] = [];
|
||||||
|
claimsToConfirmSelected: string[] = [];
|
||||||
|
description = "breakfast";
|
||||||
|
loadingConfirms = true;
|
||||||
|
someoneGave = false;
|
||||||
|
|
||||||
|
async created() {
|
||||||
|
await db.open();
|
||||||
|
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
||||||
|
this.apiServer = settings?.apiServer || "";
|
||||||
|
this.activeDid = settings?.activeDid || "";
|
||||||
|
this.allContacts = await db.contacts.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
this.loadingConfirms = true;
|
||||||
|
let currentOrPreviousSat = DateTime.now().setZone("America/Denver");
|
||||||
|
if (currentOrPreviousSat.weekday < 6) {
|
||||||
|
// it's not Saturday or Sunday,
|
||||||
|
// so move back one week before setting to the Saturday
|
||||||
|
currentOrPreviousSat = currentOrPreviousSat.minus({ week: 1 });
|
||||||
|
}
|
||||||
|
const eventStartDateObj = currentOrPreviousSat
|
||||||
|
.set({ weekday: 6 })
|
||||||
|
.set({ hour: 9 })
|
||||||
|
.startOf("hour");
|
||||||
|
|
||||||
|
// Hack, but full ISO pushes the length to 340 which crashes verifyJWT!
|
||||||
|
const todayOrPreviousStartDate =
|
||||||
|
eventStartDateObj.toISO({
|
||||||
|
suppressMilliseconds: true,
|
||||||
|
}) || "";
|
||||||
|
|
||||||
|
await accountsDB.open();
|
||||||
|
const allAccounts = await accountsDB.accounts.toArray();
|
||||||
|
this.allMyDids = allAccounts.map((acc) => acc.did);
|
||||||
|
const account: Account | undefined = await accountsDB.accounts
|
||||||
|
.where("did")
|
||||||
|
.equals(this.activeDid)
|
||||||
|
.first();
|
||||||
|
const identity: IIdentifier = JSON.parse(
|
||||||
|
(account?.identity as string) || "null",
|
||||||
|
);
|
||||||
|
const headers = {
|
||||||
|
Authorization: "Bearer " + (await accessToken(identity)),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
this.apiServer +
|
||||||
|
"/api/claim/?" +
|
||||||
|
"issuedAt_greaterThanOrEqualTo=" +
|
||||||
|
encodeURIComponent(todayOrPreviousStartDate) +
|
||||||
|
"&excludeConfirmations=true",
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.log("Bad response", response);
|
||||||
|
throw new Error("Bad response when retrieving claims.");
|
||||||
|
}
|
||||||
|
await response.json().then((data) => {
|
||||||
|
const dataByOthers = R.reject(
|
||||||
|
(claim: GenericServerRecord) => claim.issuer === this.activeDid,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
const dataByOthersWithoutHidden = R.reject(
|
||||||
|
containsHiddenDid,
|
||||||
|
dataByOthers,
|
||||||
|
);
|
||||||
|
this.claimsToConfirm = dataByOthersWithoutHidden;
|
||||||
|
this.claimCountWithHidden =
|
||||||
|
dataByOthers.length - dataByOthersWithoutHidden.length;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "There was an error retrieving today's claims to confirm.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.loadingConfirms = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickLoadClaim(jwtId: string) {
|
||||||
|
const route = {
|
||||||
|
path: "/claim/" + encodeURIComponent(jwtId),
|
||||||
|
};
|
||||||
|
this.$router.push(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
async record() {
|
||||||
|
try {
|
||||||
|
const identity = await libsUtil.getIdentity(this.activeDid);
|
||||||
|
|
||||||
|
// in parallel, make a confirmation for each selected claim and send them all to the server
|
||||||
|
const confirmResults = await Promise.allSettled(
|
||||||
|
this.claimsToConfirmSelected.map(async (jwtId) => {
|
||||||
|
const record = this.claimsToConfirm.find(
|
||||||
|
(claim) => claim.id === jwtId,
|
||||||
|
);
|
||||||
|
if (!record) {
|
||||||
|
return { type: "error", error: "Record not found." };
|
||||||
|
}
|
||||||
|
const identity = await libsUtil.getIdentity(this.activeDid);
|
||||||
|
return createAndSubmitConfirmation(
|
||||||
|
identity,
|
||||||
|
record.claim as GenericVerifiableCredential,
|
||||||
|
record.id,
|
||||||
|
record.handleId,
|
||||||
|
this.apiServer,
|
||||||
|
axios,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// check for any rejected confirmations
|
||||||
|
const confirmsSucceeded = confirmResults.filter(
|
||||||
|
(result) =>
|
||||||
|
result.status === "fulfilled" && result.value.type === "success",
|
||||||
|
);
|
||||||
|
if (confirmsSucceeded.length < this.claimsToConfirmSelected.length) {
|
||||||
|
console.error("Error sending confirmations:", confirmResults);
|
||||||
|
const howMany = confirmsSucceeded.length === 0 ? "all" : "some";
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: `There was an error sending ${howMany} of the confirmations.`,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now send the give for the description
|
||||||
|
let giveSucceeded = false;
|
||||||
|
if (this.someoneGave) {
|
||||||
|
const giveResult = await createAndSubmitGive(
|
||||||
|
axios,
|
||||||
|
this.apiServer,
|
||||||
|
identity,
|
||||||
|
undefined,
|
||||||
|
this.activeDid,
|
||||||
|
this.description,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
BVC_MEETUPS_PROJECT_CLAIM_ID,
|
||||||
|
);
|
||||||
|
giveSucceeded = giveResult.type === "success";
|
||||||
|
if (!giveSucceeded) {
|
||||||
|
console.error("Error sending give:", giveResult);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text:
|
||||||
|
(giveResult as ErrorResult)?.error?.userMessage ||
|
||||||
|
"There was an error sending that give.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirmsSucceeded.length > 0 || giveSucceeded) {
|
||||||
|
const confirms =
|
||||||
|
confirmsSucceeded.length === 1 ? "confirmation" : "confirmations";
|
||||||
|
const actions =
|
||||||
|
confirmsSucceeded.length > 0 && giveSucceeded
|
||||||
|
? `Your ${confirms} and that give have been recorded.`
|
||||||
|
: giveSucceeded
|
||||||
|
? "That give has been recorded."
|
||||||
|
: "Your " +
|
||||||
|
confirms +
|
||||||
|
" " +
|
||||||
|
(confirmsSucceeded.length === 1 ? "has" : "have") +
|
||||||
|
" been recorded.";
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Success",
|
||||||
|
text: actions,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error sending claims.", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: error.userMessage || "There was an error sending claims.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
52
src/views/QuickActionBvcView.vue
Normal file
52
src/views/QuickActionBvcView.vue
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<QuickNav />
|
||||||
|
<TopMessage />
|
||||||
|
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||||
|
<!-- Back -->
|
||||||
|
<div class="text-lg text-center font-light relative px-7">
|
||||||
|
<h1
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.back()"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-left" class="fa-fw"></fa>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Heading -->
|
||||||
|
<h1 id="ViewHeading" class="text-4xl text-center font-light px-4 mb-4">
|
||||||
|
Bountiful Voluntaryist Community Actions
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'quick-action-bvc-begin' }"
|
||||||
|
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
|
||||||
|
>
|
||||||
|
Beginning of Meeting
|
||||||
|
</router-link>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'quick-action-bvc-end' }"
|
||||||
|
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
|
||||||
|
>
|
||||||
|
End of Meeting
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
QuickNav,
|
||||||
|
TopMessage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class QuickActionBvcView extends Vue {}
|
||||||
|
</script>
|
||||||
@@ -105,21 +105,15 @@ import {
|
|||||||
LTileLayer,
|
LTileLayer,
|
||||||
} from "@vue-leaflet/vue-leaflet";
|
} from "@vue-leaflet/vue-leaflet";
|
||||||
|
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { db } from "@/db/index";
|
import { db } from "@/db/index";
|
||||||
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
|
||||||
|
|
||||||
const DEFAULT_LAT_LONG_DIFF = 0.01;
|
const DEFAULT_LAT_LONG_DIFF = 0.01;
|
||||||
const WORLD_ZOOM = 2;
|
const WORLD_ZOOM = 2;
|
||||||
const DEFAULT_ZOOM = 2;
|
const DEFAULT_ZOOM = 2;
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
QuickNav,
|
QuickNav,
|
||||||
@@ -130,7 +124,7 @@ interface Notification {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class DiscoverView extends Vue {
|
export default class DiscoverView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
isChoosingSearchBox = false;
|
isChoosingSearchBox = false;
|
||||||
isNewMarkerSet = false;
|
isNewMarkerSet = false;
|
||||||
|
|||||||
@@ -65,25 +65,20 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { accountsDB, db } from "@/db/index";
|
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
|
import { accountsDB, db } from "@/db/index";
|
||||||
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
|
||||||
interface Account {
|
interface Account {
|
||||||
mnemonic: string;
|
mnemonic: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class SeedBackupView extends Vue {
|
export default class SeedBackupView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeAccount: Account | null | undefined = null;
|
activeAccount: Account | null | undefined = null;
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
|
|||||||
@@ -53,8 +53,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
|
import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
import { World } from "@/components/World/World.js";
|
import { World } from "@/components/World/World.js";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
|
|
||||||
interface RendererSVGType {
|
interface RendererSVGType {
|
||||||
domElement: Element;
|
domElement: Element;
|
||||||
@@ -64,16 +66,9 @@ interface Dictionary<T> {
|
|||||||
[key: string]: T;
|
[key: string]: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { World, QuickNav } })
|
@Component({ components: { World, QuickNav } })
|
||||||
export default class StatisticsView extends Vue {
|
export default class StatisticsView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
world: World;
|
world: World;
|
||||||
worldProperties: Dictionary<number> = {};
|
worldProperties: Dictionary<number> = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user