Compare commits
53 Commits
ui-fixes-2
...
333ac773f6
| Author | SHA1 | Date | |
|---|---|---|---|
| 333ac773f6 | |||
| 1459719a47 | |||
| 5994365a6c | |||
| 28f72640d7 | |||
| ab81648aca | |||
| 5828a290c7 | |||
| 78fab735e6 | |||
| 2ae165d56f | |||
| 0fbd1ad51a | |||
| d49bf61524 | |||
| 1a80bbb714 | |||
| 5a2a8659f7 | |||
| 0632fb9b39 | |||
| 640d273646 | |||
| 8f4289c14d | |||
| 7f56c90d97 | |||
| 8e1daf7015 | |||
|
|
e90a0be6d9 | ||
| 489bb76a60 | |||
| 2ca33bb9eb | |||
| 121181c6a1 | |||
| e4cf79b558 | |||
| 7692cc2b35 | |||
|
|
0b4f2484f7 | ||
|
|
cfd53bc186 | ||
|
|
7f66addfe3 | ||
|
|
4635c1ac48 | ||
|
|
55da3d0b1c | ||
|
|
dce4d3cc72 | ||
|
|
e028197a2a | ||
|
|
0dab475d8b | ||
|
|
4e227fc07a | ||
| 2dfc8fedaa | |||
| 035f2a5b04 | |||
| 09dccc34d6 | |||
| b28104af5b | |||
| 3a07e31d63 | |||
| 35455e6648 | |||
| 0e2c5af16e | |||
| 1fc5b0ea2b | |||
| ca240ab795 | |||
| 01b5ca6ec8 | |||
| 6f49260c1e | |||
| 38f44771e9 | |||
| be2154f847 | |||
| 4ad4cab25e | |||
| e020caaa50 | |||
| 40d12b1f9c | |||
| 28754bdfb1 | |||
| 2b8f9579f1 | |||
| 6dc0c2cd58 | |||
| cc6d0958dc | |||
| 7ce00b86e8 |
@@ -1,4 +1,4 @@
|
|||||||
# Only the variables that start with VUE_APP_ are seen in the application process.env in Vue.
|
# Only the variables that start with VITE_ are seen in the application process.env in Vue.
|
||||||
VUE_APP_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01GXYPFF7FA03NXKPYY142PY4H
|
VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01GXYPFF7FA03NXKPYY142PY4H
|
||||||
VUE_APP_DEFAULT_ENDORSER_API_SERVER=https://api.endorser.ch
|
VITE_DEFAULT_ENDORSER_API_SERVER=https://api.endorser.ch
|
||||||
VUE_APP_DEFAULT_IMAGE_API_SERVER=https://image-api.timesafari.app
|
VITE_DEFAULT_IMAGE_API_SERVER=https://image-api.timesafari.app
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
node: true,
|
node: true,
|
||||||
|
es2022: true,
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
"plugin:vue/vue3-essential",
|
"plugin:vue/vue3-essential",
|
||||||
@@ -9,9 +10,9 @@ module.exports = {
|
|||||||
"@vue/typescript/recommended",
|
"@vue/typescript/recommended",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended",
|
||||||
],
|
],
|
||||||
parserOptions: {
|
// parserOptions: {
|
||||||
ecmaVersion: 2020,
|
// ecmaVersion: 2020,
|
||||||
},
|
// },
|
||||||
rules: {
|
rules: {
|
||||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ npm install
|
|||||||
|
|
||||||
### Compiles and hot-reloads for development
|
### Compiles and hot-reloads for development
|
||||||
```
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Builds the production app
|
||||||
|
```
|
||||||
npm run serve
|
npm run serve
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -42,7 +47,7 @@ npm run lint
|
|||||||
```
|
```
|
||||||
# (See .env.development for more details.)
|
# (See .env.development for more details.)
|
||||||
# The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.
|
# The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.
|
||||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VUE_APP_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HNTZYJJXTGT0EZS3VEJGX7AK VUE_APP_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VUE_APP_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app npm run build
|
VITE_TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HNTZYJJXTGT0EZS3VEJGX7AK VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
* Production
|
* Production
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: ["@vue/cli-plugin-babel/preset"],
|
|
||||||
};
|
|
||||||
17
index.html
Normal file
17
index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<title>TimeSafari</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but TimeSafari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19077
package-lock.json
generated
19077
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
104
package.json
104
package.json
@@ -3,93 +3,89 @@
|
|||||||
"version": "0.3.7-beta",
|
"version": "0.3.7-beta",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"dev": "vite",
|
||||||
"build": "vue-cli-service build",
|
"serve": "vite preview",
|
||||||
"lint": "vue-cli-service lint"
|
"build": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && vite build",
|
||||||
|
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
||||||
|
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dicebear/collection": "^5.3.5",
|
"@dicebear/collection": "^5.4.1",
|
||||||
"@dicebear/core": "^5.3.5",
|
"@dicebear/core": "^5.4.1",
|
||||||
"@ethersproject/hdnode": "^5.7.0",
|
"@ethersproject/hdnode": "^5.7.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
"@fortawesome/vue-fontawesome": "^3.0.6",
|
||||||
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
||||||
"@tweenjs/tween.js": "^21.0.0",
|
"@tweenjs/tween.js": "^21.1.1",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@veramo/core": "^5.4.1",
|
"@veramo/core": "^5.6.0",
|
||||||
"@veramo/credential-w3c": "^5.4.1",
|
"@veramo/credential-w3c": "^5.6.0",
|
||||||
"@veramo/data-store": "^5.4.1",
|
"@veramo/data-store": "^5.6.0",
|
||||||
"@veramo/did-manager": "^5.4.1",
|
"@veramo/did-manager": "^5.6.0",
|
||||||
"@veramo/did-provider-ethr": "^5.4.1",
|
"@veramo/did-provider-ethr": "^5.6.0",
|
||||||
"@veramo/did-resolver": "^5.4.1",
|
"@veramo/did-resolver": "^5.6.0",
|
||||||
"@veramo/key-manager": "^5.4.1",
|
"@veramo/key-manager": "^5.6.0",
|
||||||
"@vueuse/core": "^10.4.1",
|
"@vueuse/core": "^10.9.0",
|
||||||
"@zxing/text-encoding": "^0.9.0",
|
"@zxing/text-encoding": "^0.9.0",
|
||||||
"axios": "^1.5.0",
|
"axios": "^1.6.8",
|
||||||
"buffer": "^6.0.3",
|
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"core-js": "^3.32.1",
|
"dexie": "^3.2.7",
|
||||||
"dexie": "^3.2.4",
|
"dexie-export-import": "^4.1.1",
|
||||||
"dexie-export-import": "^4.0.7",
|
"did-jwt": "^7.4.7",
|
||||||
"did-jwt": "^7.2.7",
|
"ethereum-cryptography": "^2.1.3",
|
||||||
"ethereum-cryptography": "^2.1.2",
|
|
||||||
"ethereumjs-util": "^7.1.5",
|
"ethereumjs-util": "^7.1.5",
|
||||||
"ethr-did-resolver": "^8.1.2",
|
"ethr-did-resolver": "^8.1.2",
|
||||||
"git-describe": "^4.1.1",
|
"git-describe": "^4.1.1",
|
||||||
"jdenticon": "^3.2.0",
|
"jdenticon": "^3.2.0",
|
||||||
"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.7.0",
|
||||||
|
"lru-cache": "^10.2.0",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
"merkletreejs": "^0.3.11",
|
"merkletreejs": "^0.3.11",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.30.1",
|
||||||
"notiwind": "^2.0.2",
|
"notiwind": "^2.0.2",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"pina": "^0.20.2204228",
|
"pina": "^0.20.2204228",
|
||||||
"pinia-plugin-persistedstate": "^3.2.0",
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
"qr-code-generator-vue3": "^1.4.21",
|
"qr-code-generator-vue3": "^1.4.21",
|
||||||
"ramda": "^0.29.0",
|
"ramda": "^0.29.1",
|
||||||
"readable-stream": "^4.4.2",
|
"readable-stream": "^4.5.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.14",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"simple-vue-camera": "^1.1.3",
|
"simple-vue-camera": "^1.1.3",
|
||||||
"three": "^0.156.1",
|
"three": "^0.156.1",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.4.21",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^3.0.2",
|
"vue-facing-decorator": "^3.0.4",
|
||||||
"vue-qrcode-reader": "^5.4.1",
|
"vue-qrcode-reader": "^5.5.3",
|
||||||
"vue-router": "^4.2.4",
|
"vue-router": "^4.3.0",
|
||||||
"web-did-resolver": "^2.0.27"
|
"web-did-resolver": "^2.0.27"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/leaflet": "^1.9.4",
|
"@types/leaflet": "^1.9.8",
|
||||||
"@types/ramda": "^0.29.3",
|
"@types/ramda": "^0.29.11",
|
||||||
"@types/three": "^0.155.1",
|
"@types/three": "^0.155.1",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.6.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^6.6.0",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||||
"@vue/cli-plugin-babel": "~5.0.8",
|
|
||||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
|
||||||
"@vue/cli-plugin-pwa": "~5.0.8",
|
|
||||||
"@vue/cli-plugin-router": "~5.0.8",
|
|
||||||
"@vue/cli-plugin-typescript": "~5.0.8",
|
|
||||||
"@vue/cli-plugin-vuex": "~5.0.8",
|
|
||||||
"@vue/cli-service": "~5.0.8",
|
|
||||||
"@vue/eslint-config-typescript": "^11.0.3",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"autoprefixer": "^10.4.15",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^8.53.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.23.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"postcss": "^8.4.29",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "^3.1.0",
|
"prettier": "^3.2.5",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "~5.2.2"
|
"typescript": "~5.2.2",
|
||||||
|
"vite": "^5.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,37 @@
|
|||||||
|
|
||||||
tasks :
|
tasks :
|
||||||
|
|
||||||
|
- fix the notification link to the app
|
||||||
|
- 01 change scanning flow - allow them to stay on the QR/scanning screen after scanning someone
|
||||||
|
|
||||||
- 24 contextual tutorials https://docs.google.com/document/d/11C_K3RM0rgo0onih20KFhcIzukZyq_CRWqaWX5om_kM/edit#heading=h.iwiwcydou5hw
|
- 24 contextual tutorials https://docs.google.com/document/d/11C_K3RM0rgo0onih20KFhcIzukZyq_CRWqaWX5om_kM/edit#heading=h.iwiwcydou5hw
|
||||||
|
|
||||||
- 24 Move to Vite
|
- 24 Move to Vite assignee:jason
|
||||||
|
|
||||||
|
- feeds - add "remote" filter, if they choose 'visible' then warn that they won't see any others, cache list & don't reload front page on change
|
||||||
|
|
||||||
|
- .1 add shortcut from project (etc?) to the public project page in a browser
|
||||||
- .1 add KindSpring link to ideas
|
- .1 add KindSpring link to ideas
|
||||||
- .1 on feed, don't show "to someone anonymous" if it's to a project
|
- .1 on feed, don't show "to someone anonymous" if it's to a project
|
||||||
- .1 on ideas, put an "x" to close it assignee-group:ui
|
|
||||||
- 16 save data backups in Google
|
- 16 save data backups in Google
|
||||||
- 16 generate and use passkeys for identities
|
- 16 generate and use passkeys for identities
|
||||||
- .5 show "give" buttons (eg. from anonymous) even if they can't give, greyed out, and give them a warning and instructions
|
- .5 show "give" buttons (eg. from anonymous) even if they can't give, greyed out, and give them a warning and instructions
|
||||||
- .2 when adding a claim on home screen, push that claim to the top of the list
|
- .2 when adding a claim on home screen, push that claim to the top of the list
|
||||||
|
|
||||||
- .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
|
||||||
- .2 fix bottom of project selection map, where the icons are hidden but a tap goes to the icon's page assignee-group:ui
|
|
||||||
- .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)
|
- .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 don't show a warning on a totally new project when the authorized agent is set
|
||||||
- .2 anchor hash into BTC
|
- .2 anchor hash into BTC
|
||||||
- .2 list the "show more" contacts alphabetically
|
- .2 list the "show more" contacts alphabetically
|
||||||
|
- .5 add back the explicit wait for browser subscription timing problems?
|
||||||
|
|
||||||
- .5 make Time Safari a share_target for images
|
- .5 make Time Safari a share_target for images
|
||||||
|
|
||||||
- 08 add image on profile
|
- 08 add image on profile
|
||||||
|
|
||||||
- ask to detect location & record it in settings
|
- 01 ask to detect location & record it in settings
|
||||||
- if personal location is set, show potential local affiliations
|
- 01 if personal location is set, show potential local affiliations
|
||||||
|
- 02 refactor the buttons for chosing a search location so that the actions are clear assignee-group:ui
|
||||||
|
|
||||||
- 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
|
||||||
@@ -94,7 +100,7 @@ tasks :
|
|||||||
- .3 check that Android shows "back" buttons on screens without bottom tray
|
- .3 check that Android shows "back" buttons on screens without bottom tray
|
||||||
- .1 Make give description text box into something that expands as they type?
|
- .1 Make give description text box into something that expands as they type?
|
||||||
- .2 Show a warning if both giver and recipient are the same (but still allow?)
|
- .2 Show a warning if both giver and recipient are the same (but still allow?)
|
||||||
- 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui
|
- .5 Shrink the buttons on project pages so they don't expand to the width of the screen assignee-group:ui
|
||||||
- .5 Display a more appealing confirmation on the map when erasing the marker
|
- .5 Display a more appealing confirmation on the map when erasing the marker
|
||||||
- .5 remove references to localStorage for projectId (now that it's pulling from the path)
|
- .5 remove references to localStorage for projectId (now that it's pulling from the path)
|
||||||
- switch some checks for activeDid to check for isRegistered
|
- switch some checks for activeDid to check for isRegistered
|
||||||
@@ -121,7 +127,7 @@ tasks :
|
|||||||
- 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
|
- 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
|
||||||
|
|
||||||
- .5 show seed phrase in a QR code for transfer to another device
|
- .5 show seed phrase in a QR code for transfer to another device
|
||||||
- .5 on DiscoverView, switch to a filter UI (eg. just from friend
|
- .5 on DiscoverView, switch to a filter UI (eg. just from friend)
|
||||||
- .5 don't show "Offer" on project screen if they aren't registered
|
- .5 don't show "Offer" on project screen if they aren't registered
|
||||||
- 01 especially for iOS, check for new version & update, eg. https://stackoverflow.com/questions/52221805/any-way-yet-to-auto-update-or-just-clear-the-cache-on-a-pwa-on-ios
|
- 01 especially for iOS, check for new version & update, eg. https://stackoverflow.com/questions/52221805/any-way-yet-to-auto-update-or-just-clear-the-cache-on-a-pwa-on-ios
|
||||||
|
|
||||||
@@ -142,7 +148,6 @@ tasks :
|
|||||||
- for subtasks: fulfills (is it really the same?), feeds, contributes to, supplies, boosts, advances
|
- for subtasks: fulfills (is it really the same?), feeds, contributes to, supplies, boosts, advances
|
||||||
- for blocking: blocks, precedes, comes before, is sought by -- vs follows, seeks, builds on ("contributes to" isn't specific enough, "succeeds" has different, possibly confusing meaning)
|
- for blocking: blocks, precedes, comes before, is sought by -- vs follows, seeks, builds on ("contributes to" isn't specific enough, "succeeds" has different, possibly confusing meaning)
|
||||||
|
|
||||||
- .5 fit as many icons as possible on home & project view screens but only going halfway down the page assignee-group:ui
|
|
||||||
- .5 Replace Gifted/Give in ContactsView with GiftedDialog
|
- .5 Replace Gifted/Give in ContactsView with GiftedDialog
|
||||||
|
|
||||||
- Stats :
|
- Stats :
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>
|
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
|
||||||
</noscript>
|
|
||||||
<div id="app"></div>
|
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
161
src/App.vue
161
src/App.vue
@@ -191,7 +191,7 @@
|
|||||||
>
|
>
|
||||||
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
||||||
<p v-if="serviceWorkerReady" class="text-lg mb-4">
|
<p v-if="serviceWorkerReady" class="text-lg mb-4">
|
||||||
Would you like to <b>turn on</b> notifications for this app?
|
Would you like to be notified of new activity once a day?
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-lg mb-4">
|
<p v-else class="text-lg mb-4">
|
||||||
Waiting for system initialization, which may take up to 10
|
Waiting for system initialization, which may take up to 10
|
||||||
@@ -199,22 +199,42 @@
|
|||||||
<fa icon="spinner" spin />
|
<fa icon="spinner" spin />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<div v-if="serviceWorkerReady">
|
||||||
v-if="serviceWorkerReady"
|
<span class="flex flex-row justify-center">
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
<span class="mt-2">Yes, tell me at: </span>
|
||||||
@click="
|
<input
|
||||||
close(notification.id);
|
type="number"
|
||||||
turnOnNotifications();
|
class="rounded-l border border-r-0 border-slate-400 ml-2 mt-2 px-2 py-2 text-center w-20"
|
||||||
"
|
v-model="hourInput"
|
||||||
>
|
/>
|
||||||
Turn on Notifications
|
<span
|
||||||
</button>
|
class="rounded-r border border-slate-400 bg-slate-200 text-center text-blue-500 mt-2 px-2 py-2 w-20"
|
||||||
|
@click="hourAm = !hourAm"
|
||||||
|
>
|
||||||
|
<span v-if="hourAm"> AM <fa icon="chevron-down" /> </span>
|
||||||
|
<span v-else> PM <fa icon="chevron-up" /> </span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white mt-2 px-2 py-2 rounded-md"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
if (checkHour()) {
|
||||||
|
close(notification.id);
|
||||||
|
turnOnNotifications();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Turn on Daily Message
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
@click="close(notification.id)"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white mt-4 px-2 py-2 rounded-md"
|
||||||
>
|
>
|
||||||
Maybe Later
|
No, Not Now
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -297,8 +317,11 @@
|
|||||||
<style></style>
|
<style></style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component } from "vue-facing-decorator";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { Vue, Component } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import * as libsUtil from "@/libs/util";
|
||||||
|
|
||||||
interface ServiceWorkerMessage {
|
interface ServiceWorkerMessage {
|
||||||
type: string;
|
type: string;
|
||||||
data: string;
|
data: string;
|
||||||
@@ -322,6 +345,10 @@ interface VapidResponse {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PushSubscriptionWithTime extends PushSubscriptionJSON {
|
||||||
|
notifyTime: { utcHour: number };
|
||||||
|
}
|
||||||
|
|
||||||
import { DEFAULT_PUSH_SERVER, NotificationIface } 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";
|
||||||
@@ -332,7 +359,9 @@ export default class App extends Vue {
|
|||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
b64 = "";
|
b64 = "";
|
||||||
serviceWorkerReady = false;
|
hourAm = true;
|
||||||
|
hourInput = "8";
|
||||||
|
serviceWorkerReady = true;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
@@ -343,25 +372,29 @@ export default class App extends Vue {
|
|||||||
pushUrl = settings.webPushServer;
|
pushUrl = settings.webPushServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
await axios
|
if (pushUrl.startsWith("http://localhost")) {
|
||||||
.get(pushUrl + "/web-push/vapid")
|
console.log("Not checking for VAPID in this local environment.");
|
||||||
.then((response: VapidResponse) => {
|
} else {
|
||||||
this.b64 = response.data?.vapidKey || "";
|
await axios
|
||||||
console.log("Got vapid key:", this.b64);
|
.get(pushUrl + "/web-push/vapid")
|
||||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
.then((response: VapidResponse) => {
|
||||||
console.log("New service worker is now controlling the page");
|
this.b64 = response.data?.vapidKey || "";
|
||||||
|
console.log("Got vapid key:", this.b64);
|
||||||
|
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||||
|
console.log("New service worker is now controlling the page");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
if (!this.b64) {
|
||||||
if (!this.b64) {
|
this.$notify(
|
||||||
this.$notify(
|
{
|
||||||
{
|
group: "alert",
|
||||||
group: "alert",
|
type: "danger",
|
||||||
type: "danger",
|
title: "Error Setting Notifications",
|
||||||
title: "Error Setting Notifications",
|
text: "Could not set notifications.",
|
||||||
text: "Could not set notifications.",
|
},
|
||||||
},
|
-1,
|
||||||
-1,
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (window.location.host.startsWith("localhost")) {
|
if (window.location.host.startsWith("localhost")) {
|
||||||
@@ -461,6 +494,48 @@ export default class App extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this allows us to show an error without closing the dialog
|
||||||
|
checkHour() {
|
||||||
|
if (!libsUtil.isNumeric(this.hourInput)) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Not a Number",
|
||||||
|
text: "The time must be an hour number.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hourNum = libsUtil.numberOrZero(this.hourInput);
|
||||||
|
if (!Number.isInteger(hourNum)) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Not a Whole Number",
|
||||||
|
text: "The time must be a whole hour number.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hourNum < 1 || 12 < hourNum) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Not a Whole Number",
|
||||||
|
text: "The time must be an hour between 1 and 12.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public async turnOnNotifications() {
|
public async turnOnNotifications() {
|
||||||
return this.askPermission()
|
return this.askPermission()
|
||||||
.then((permission) => {
|
.then((permission) => {
|
||||||
@@ -486,13 +561,25 @@ export default class App extends Vue {
|
|||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
this.sendSubscriptionToServer(subscription);
|
// we already checked that this is a valid hour number
|
||||||
return subscription;
|
const rawHourNum = libsUtil.numberOrZero(this.hourInput);
|
||||||
|
const adjHourNum = rawHourNum + (this.hourAm ? 0 : 12);
|
||||||
|
const hourNum = adjHourNum % 24;
|
||||||
|
const utcHour =
|
||||||
|
hourNum + Math.round(new Date().getTimezoneOffset() / 60);
|
||||||
|
const finalUtcHour = (utcHour + (utcHour < 0 ? 24 : 0)) % 24;
|
||||||
|
|
||||||
|
const subscriptionWithTime: PushSubscriptionWithTime = {
|
||||||
|
notifyTime: { utcHour: finalUtcHour },
|
||||||
|
...subscription.toJSON(),
|
||||||
|
};
|
||||||
|
await this.sendSubscriptionToServer(subscriptionWithTime);
|
||||||
|
return subscriptionWithTime;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Subscription object is not available.");
|
throw new Error("Subscription object is not available.");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(async (subscription) => {
|
.then(async (subscription: PushSubscriptionWithTime) => {
|
||||||
console.log(
|
console.log(
|
||||||
"Subscription data sent to server and all finished successfully.",
|
"Subscription data sent to server and all finished successfully.",
|
||||||
);
|
);
|
||||||
@@ -583,7 +670,7 @@ export default class App extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private sendSubscriptionToServer(
|
private sendSubscriptionToServer(
|
||||||
subscription: PushSubscription,
|
subscription: PushSubscriptionWithTime,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log("About to send subscription...", subscription);
|
console.log("About to send subscription...", subscription);
|
||||||
return fetch("/web-push/subscribe", {
|
return fetch("/web-push/subscribe", {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap');
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap');
|
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
html {
|
html {
|
||||||
font-family: 'Work Sans', ui-sans-serif, system-ui, sans-serif !important;
|
font-family: 'Work Sans', ui-sans-serif, system-ui, sans-serif !important;
|
||||||
|
|||||||
219
src/components/FeedFilters.vue
Normal file
219
src/components/FeedFilters.vue
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="visible" id="dialogFeedFilters" class="dialog-overlay">
|
||||||
|
<div class="dialog">
|
||||||
|
<h1 class="text-xl font-bold text-center mb-4">Feed Filters</h1>
|
||||||
|
|
||||||
|
<p class="mb-4 font-bold">Show only activities that…</p>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 gap-2">
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between cursor-pointer"
|
||||||
|
@click="toggleHasVisibleDid()"
|
||||||
|
>
|
||||||
|
<!-- label -->
|
||||||
|
<div>Include someone visible to me</div>
|
||||||
|
<!-- toggle -->
|
||||||
|
<div class="relative ml-2">
|
||||||
|
<!-- input -->
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="hasVisibleDid"
|
||||||
|
name="toggleFilterFromMyContacts"
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<em>or</em>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between cursor-pointer"
|
||||||
|
@click="
|
||||||
|
hasSearchBox
|
||||||
|
? toggleNearby()
|
||||||
|
: $router.push({ name: 'search-area' })
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- label -->
|
||||||
|
<div>Are nearby</div>
|
||||||
|
<!-- toggle -->
|
||||||
|
<div v-if="hasSearchBox" class="relative ml-2">
|
||||||
|
<!-- input -->
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="isNearby"
|
||||||
|
name="toggleFilterNearby"
|
||||||
|
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>
|
||||||
|
<div v-else class="relative ml-2">
|
||||||
|
<button class="ml-2 px-4 py-2 rounded-md bg-blue-200 text-blue-500">
|
||||||
|
Select Location
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-2 mt-4">
|
||||||
|
<button
|
||||||
|
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
|
@click="setAll()"
|
||||||
|
>
|
||||||
|
Set All
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
|
@click="clearAll()"
|
||||||
|
>
|
||||||
|
Clear All
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
|
@click="done()"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Component } from "vue-facing-decorator";
|
||||||
|
import {
|
||||||
|
LMap,
|
||||||
|
LMarker,
|
||||||
|
LRectangle,
|
||||||
|
LTileLayer,
|
||||||
|
} from "@vue-leaflet/vue-leaflet";
|
||||||
|
|
||||||
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
import { db } from "@/db/index";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
LRectangle,
|
||||||
|
LMap,
|
||||||
|
LMarker,
|
||||||
|
LTileLayer,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class FeedFilters extends Vue {
|
||||||
|
onCloseIfChanged = () => {};
|
||||||
|
hasSearchBox = false;
|
||||||
|
hasVisibleDid = false;
|
||||||
|
isNearby = false;
|
||||||
|
settingChanged = false;
|
||||||
|
visible = false;
|
||||||
|
|
||||||
|
async open(onCloseIfChanged: () => void) {
|
||||||
|
this.onCloseIfChanged = onCloseIfChanged;
|
||||||
|
|
||||||
|
await db.open();
|
||||||
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
|
this.hasVisibleDid = !!settings?.filterFeedByVisible;
|
||||||
|
this.isNearby = !!settings?.filterFeedByNearby;
|
||||||
|
if (settings?.searchBoxes && settings.searchBoxes.length > 0) {
|
||||||
|
this.hasSearchBox = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settingChanged = false;
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleHasVisibleDid() {
|
||||||
|
this.settingChanged = true;
|
||||||
|
this.hasVisibleDid = !this.hasVisibleDid;
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
filterFeedByVisible: this.hasVisibleDid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleNearby() {
|
||||||
|
this.settingChanged = true;
|
||||||
|
this.isNearby = !this.isNearby;
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
filterFeedByNearby: this.isNearby,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearAll() {
|
||||||
|
if (this.hasVisibleDid || this.isNearby) {
|
||||||
|
this.settingChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
filterFeedByNearby: false,
|
||||||
|
filterFeedByVisible: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hasVisibleDid = false;
|
||||||
|
this.isNearby = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setAll() {
|
||||||
|
if (!this.hasVisibleDid || !this.isNearby) {
|
||||||
|
this.settingChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
filterFeedByNearby: true,
|
||||||
|
filterFeedByVisible: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hasVisibleDid = true;
|
||||||
|
this.isNearby = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (this.settingChanged) {
|
||||||
|
this.onCloseIfChanged();
|
||||||
|
}
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
done() {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dialogFeedFilters.dialog-overlay {
|
||||||
|
z-index: 99999;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
background-color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -20,11 +20,11 @@ export enum AppString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_ENDORSER_API_SERVER =
|
export const DEFAULT_ENDORSER_API_SERVER =
|
||||||
process.env.VUE_APP_DEFAULT_ENDORSER_API_SERVER ||
|
import.meta.env.VITE_DEFAULT_ENDORSER_API_SERVER ||
|
||||||
AppString.TEST_ENDORSER_API_SERVER;
|
AppString.TEST_ENDORSER_API_SERVER;
|
||||||
|
|
||||||
export const DEFAULT_IMAGE_API_SERVER =
|
export const DEFAULT_IMAGE_API_SERVER =
|
||||||
process.env.VUE_APP_DEFAULT_IMAGE_API_SERVER ||
|
import.meta.env.VITE_DEFAULT_IMAGE_API_SERVER ||
|
||||||
AppString.TEST_IMAGE_API_SERVER;
|
AppString.TEST_IMAGE_API_SERVER;
|
||||||
|
|
||||||
export const DEFAULT_PUSH_SERVER =
|
export const DEFAULT_PUSH_SERVER =
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export type Settings = {
|
|||||||
|
|
||||||
activeDid?: string; // Active Decentralized ID
|
activeDid?: string; // Active Decentralized ID
|
||||||
apiServer?: string; // API server URL
|
apiServer?: string; // API server URL
|
||||||
|
|
||||||
|
filterFeedByNearby?: boolean; // filter by nearby
|
||||||
|
filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden
|
||||||
|
|
||||||
firstName?: string; // User's first name
|
firstName?: string; // User's first name
|
||||||
isRegistered?: boolean;
|
isRegistered?: boolean;
|
||||||
lastName?: string; // deprecated - put all names in firstName
|
lastName?: string; // deprecated - put all names in firstName
|
||||||
@@ -38,6 +42,10 @@ export type Settings = {
|
|||||||
webPushServer?: string; // Web Push server URL
|
webPushServer?: string; // Web Push server URL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isAnyFeedFilterOn(settings: Settings): boolean {
|
||||||
|
return !!(settings.filterFeedByNearby || settings.filterFeedByVisible);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schema for the Settings table in the database.
|
* Schema for the Settings table in the database.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export const sign = async (privateKeyHex: string) => {
|
|||||||
* The SimpleSigner returns a configured function for signing data.
|
* The SimpleSigner returns a configured function for signing data.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const signer = SimpleSigner(process.env.PRIVATE_KEY)
|
* const signer = SimpleSigner(import.meta.env.PRIVATE_KEY)
|
||||||
* signer(data, (err, signature) => {
|
* signer(data, (err, signature) => {
|
||||||
* ...
|
* ...
|
||||||
* })
|
* })
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { Axios, AxiosResponse, RawAxiosRequestHeaders } from "axios";
|
||||||
|
import * as didJwt from "did-jwt";
|
||||||
|
import { LRUCache } from "lru-cache";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
|
||||||
import * as didJwt from "did-jwt";
|
|
||||||
import { Axios, AxiosResponse } from "axios";
|
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||||
|
|
||||||
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
||||||
// the object in RegisterAction claims
|
// the object in RegisterAction claims
|
||||||
@@ -49,7 +51,7 @@ export interface GenericVerifiableCredential {
|
|||||||
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericServerRecord extends GenericVerifiableCredential {
|
export interface GenericCredWrapper extends GenericVerifiableCredential {
|
||||||
handleId?: string;
|
handleId?: string;
|
||||||
id: string;
|
id: string;
|
||||||
issuedAt: string;
|
issuedAt: string;
|
||||||
@@ -58,7 +60,7 @@ export interface GenericServerRecord extends GenericVerifiableCredential {
|
|||||||
claim: Record<string, any>;
|
claim: Record<string, any>;
|
||||||
claimType?: string;
|
claimType?: string;
|
||||||
}
|
}
|
||||||
export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = {
|
export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper = {
|
||||||
"@context": SCHEMA_ORG_CONTEXT,
|
"@context": SCHEMA_ORG_CONTEXT,
|
||||||
"@type": "",
|
"@type": "",
|
||||||
claim: {},
|
claim: {},
|
||||||
@@ -68,7 +70,7 @@ export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// a summary record; the VC is found the fullClaim field
|
// a summary record; the VC is found the fullClaim field
|
||||||
export interface GiveServerRecord {
|
export interface GiveSummaryRecord {
|
||||||
agentDid: string;
|
agentDid: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
amountConfirmed: number;
|
amountConfirmed: number;
|
||||||
@@ -83,7 +85,7 @@ export interface GiveServerRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// a summary record; the VC is found the fullClaim field
|
// a summary record; the VC is found the fullClaim field
|
||||||
export interface OfferServerRecord {
|
export interface OfferSummaryRecord {
|
||||||
amount: number;
|
amount: number;
|
||||||
amountGiven: number;
|
amountGiven: number;
|
||||||
amountGivenConfirmed: number;
|
amountGivenConfirmed: number;
|
||||||
@@ -101,7 +103,7 @@ export interface OfferServerRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// a summary record; the VC is not currently part of this record
|
// a summary record; the VC is not currently part of this record
|
||||||
export interface PlanServerRecord {
|
export interface PlanSummaryRecord {
|
||||||
agentDid?: string; // optional, if the issuer wants someone else to manage as well
|
agentDid?: string; // optional, if the issuer wants someone else to manage as well
|
||||||
description: string;
|
description: string;
|
||||||
endTime?: string;
|
endTime?: string;
|
||||||
@@ -110,6 +112,7 @@ export interface PlanServerRecord {
|
|||||||
issuerDid: string;
|
issuerDid: string;
|
||||||
locLat?: number;
|
locLat?: number;
|
||||||
locLon?: number;
|
locLon?: number;
|
||||||
|
name?: string;
|
||||||
startTime?: string;
|
startTime?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
}
|
}
|
||||||
@@ -256,6 +259,10 @@ export type CreateAndSubmitClaimResult = SuccessResult | ErrorResult;
|
|||||||
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
|
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
|
||||||
const HIDDEN_DID = "did:none:HIDDEN";
|
const HIDDEN_DID = "did:none:HIDDEN";
|
||||||
|
|
||||||
|
const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
|
||||||
|
max: 500,
|
||||||
|
});
|
||||||
|
|
||||||
export function isDid(did: string) {
|
export function isDid(did: string) {
|
||||||
return did.startsWith("did:");
|
return did.startsWith("did:");
|
||||||
}
|
}
|
||||||
@@ -269,7 +276,7 @@ export function isEmptyOrHiddenDid(did?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true for any nested string where func(input) === true
|
* @return true for any string within this primitive/object/array where func(input) === true
|
||||||
*
|
*
|
||||||
* Similar logic is found in endorser-mobile.
|
* Similar logic is found in endorser-mobile.
|
||||||
*/
|
*/
|
||||||
@@ -304,6 +311,12 @@ export function containsHiddenDid(obj: any) {
|
|||||||
return testRecursivelyOnStrings(isHiddenDid, obj);
|
return testRecursivelyOnStrings(isHiddenDid, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const containsNonHiddenDid = (obj: any) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return testRecursivelyOnStrings((s: any) => isDid(s) && !isHiddenDid(s), obj);
|
||||||
|
};
|
||||||
|
|
||||||
export function stripEndorserPrefix(claimId: string) {
|
export function stripEndorserPrefix(claimId: string) {
|
||||||
if (claimId && claimId.startsWith(ENDORSER_CH_HANDLE_PREFIX)) {
|
if (claimId && claimId.startsWith(ENDORSER_CH_HANDLE_PREFIX)) {
|
||||||
return claimId.substring(ENDORSER_CH_HANDLE_PREFIX.length);
|
return claimId.substring(ENDORSER_CH_HANDLE_PREFIX.length);
|
||||||
@@ -403,8 +416,11 @@ export function didInfoForContact(
|
|||||||
return myId
|
return myId
|
||||||
? { displayName: "You (Alt ID)", known: true }
|
? { displayName: "You (Alt ID)", known: true }
|
||||||
: isHiddenDid(did)
|
: isHiddenDid(did)
|
||||||
? { displayName: "Someone Outside Your Network", known: false }
|
? { displayName: "Someone Totally Outside Your View", known: false }
|
||||||
: { displayName: "Someone Outside Contacts", known: false };
|
: {
|
||||||
|
displayName: "Someone Visible But Outside Your Contact List",
|
||||||
|
known: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,6 +439,71 @@ export function didInfo(
|
|||||||
return didInfoForContact(did, activeDid, contact, allMyDids).displayName;
|
return didInfoForContact(did, activeDid, contact, allMyDids).displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getHeaders(identity: IIdentifier | null) {
|
||||||
|
const headers: RawAxiosRequestHeaders = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
if (identity) {
|
||||||
|
const token = await accessToken(identity);
|
||||||
|
headers["Authorization"] = "Bearer " + token;
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param handleId nullable -- which means that "undefined" will be returned
|
||||||
|
* @param identity nullable -- which means no private info will be returned
|
||||||
|
* @param axios
|
||||||
|
* @param apiServer
|
||||||
|
*/
|
||||||
|
export async function getPlanFromCache(
|
||||||
|
handleId: string | null,
|
||||||
|
identity: IIdentifier | null,
|
||||||
|
axios: Axios,
|
||||||
|
apiServer: string,
|
||||||
|
) {
|
||||||
|
if (!handleId) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let cred = planCache.get(handleId);
|
||||||
|
if (!cred) {
|
||||||
|
const url =
|
||||||
|
apiServer +
|
||||||
|
"/api/v2/report/plans?handleId=" +
|
||||||
|
encodeURIComponent(handleId);
|
||||||
|
const headers = await getHeaders(identity);
|
||||||
|
try {
|
||||||
|
const resp = await axios.get(url, { headers });
|
||||||
|
if (resp.status === 200 && resp.data?.data?.length > 0) {
|
||||||
|
cred = resp.data.data[0];
|
||||||
|
planCache.set(handleId, cred);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"Failed to load plan with handle",
|
||||||
|
handleId,
|
||||||
|
" Got data:",
|
||||||
|
resp.data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
"Failed to load plan with handle",
|
||||||
|
handleId,
|
||||||
|
" Got error:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cred;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setPlanInCache(
|
||||||
|
handleId: string,
|
||||||
|
planSummary: PlanSummaryRecord,
|
||||||
|
) {
|
||||||
|
planCache.set(handleId, planSummary);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
|
* For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
|
||||||
*
|
*
|
||||||
@@ -475,7 +556,7 @@ export async function createAndSubmitGive(
|
|||||||
vcClaim.image = imageUrl;
|
vcClaim.image = imageUrl;
|
||||||
}
|
}
|
||||||
return createAndSubmitClaim(
|
return createAndSubmitClaim(
|
||||||
vcClaim as GenericServerRecord,
|
vcClaim as GenericCredWrapper,
|
||||||
identity,
|
identity,
|
||||||
apiServer,
|
apiServer,
|
||||||
axios,
|
axios,
|
||||||
@@ -524,7 +605,7 @@ export async function createAndSubmitOffer(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return createAndSubmitClaim(
|
return createAndSubmitClaim(
|
||||||
vcClaim as GenericServerRecord,
|
vcClaim as GenericCredWrapper,
|
||||||
identity,
|
identity,
|
||||||
apiServer,
|
apiServer,
|
||||||
axios,
|
axios,
|
||||||
@@ -695,7 +776,7 @@ const claimSummary = (claim: Record<string, any>) => {
|
|||||||
similar code is also contained in endorser-mobile
|
similar code is also contained in endorser-mobile
|
||||||
**/
|
**/
|
||||||
export const claimSpecialDescription = (
|
export const claimSpecialDescription = (
|
||||||
record: GenericServerRecord,
|
record: GenericCredWrapper,
|
||||||
activeDid: string,
|
activeDid: string,
|
||||||
identifiers: Array<string>,
|
identifiers: Array<string>,
|
||||||
contacts: Array<Contact>,
|
contacts: Array<Contact>,
|
||||||
@@ -789,12 +870,12 @@ export const claimSpecialDescription = (
|
|||||||
"...]"
|
"...]"
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return issuer + " declared " + claimSummary(claim as GenericServerRecord);
|
return issuer + " declared " + claimSummary(claim as GenericCredWrapper);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BVC_MEETUPS_PROJECT_CLAIM_ID =
|
export const BVC_MEETUPS_PROJECT_CLAIM_ID =
|
||||||
process.env.VUE_APP_BVC_MEETUPS_PROJECT_CLAIM_ID ||
|
import.meta.env.VITE_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
|
"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) => {
|
export const bvcMeetingJoinClaim = (did: string, startTime: string) => {
|
||||||
|
|||||||
@@ -9,12 +9,9 @@ import { accountsDB, db } from "@/db/index";
|
|||||||
import { Account } from "@/db/tables/accounts";
|
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 { GenericCredWrapper, containsHiddenDid } from "@/libs/endorserServer";
|
||||||
import * as serverUtil from "@/libs/endorserServer";
|
import * as serverUtil from "@/libs/endorserServer";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const Buffer = require("buffer/").Buffer;
|
|
||||||
|
|
||||||
export const PRIVACY_MESSAGE =
|
export const PRIVACY_MESSAGE =
|
||||||
"The data you send be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to those you allow.";
|
"The data you send be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to those you allow.";
|
||||||
|
|
||||||
@@ -56,9 +53,13 @@ export function iconForUnitCode(unitCode: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// from https://stackoverflow.com/a/175787/845494
|
// from https://stackoverflow.com/a/175787/845494
|
||||||
|
// ... though it appears even this isn't precisely right so keep doing "|| 0" or something in sensitive places
|
||||||
//
|
//
|
||||||
export function isNumeric(str: string): boolean {
|
export function isNumeric(str: string): boolean {
|
||||||
return !isNaN(+str);
|
// This ignore commentary is because typescript complains when you pass a string to isNaN.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
return !isNaN(str) && !isNaN(parseFloat(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function numberOrZero(str: string): number {
|
export function numberOrZero(str: string): number {
|
||||||
@@ -69,7 +70,7 @@ 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+.-]+:/));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const giveIsConfirmable = (veriClaim: GenericServerRecord) => {
|
export const giveIsConfirmable = (veriClaim: GenericCredWrapper) => {
|
||||||
return veriClaim.claimType === "GiveAction";
|
return veriClaim.claimType === "GiveAction";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ export const doCopyTwoSecRedo = (text: string, fn: () => void) => {
|
|||||||
* @param veriClaim is expected to have fields: claim, claimType, and issuer
|
* @param veriClaim is expected to have fields: claim, claimType, and issuer
|
||||||
*/
|
*/
|
||||||
export const isGiveRecordTheUserCanConfirm = (
|
export const isGiveRecordTheUserCanConfirm = (
|
||||||
veriClaim: GenericServerRecord,
|
veriClaim: GenericCredWrapper,
|
||||||
activeDid: string,
|
activeDid: string,
|
||||||
confirmerIdList: string[] = [],
|
confirmerIdList: string[] = [],
|
||||||
) => {
|
) => {
|
||||||
@@ -101,9 +102,9 @@ export const isGiveRecordTheUserCanConfirm = (
|
|||||||
* @returns the DID of the person who offered, or undefined if hidden
|
* @returns the DID of the person who offered, or undefined if hidden
|
||||||
* @param veriClaim is expected to have fields: claim and issuer
|
* @param veriClaim is expected to have fields: claim and issuer
|
||||||
*/
|
*/
|
||||||
export const offerGiverDid: (
|
export const offerGiverDid: (arg0: GenericCredWrapper) => string | undefined = (
|
||||||
arg0: GenericServerRecord,
|
veriClaim,
|
||||||
) => string | undefined = (veriClaim) => {
|
) => {
|
||||||
let giver;
|
let giver;
|
||||||
if (
|
if (
|
||||||
veriClaim.claim.offeredBy?.identifier &&
|
veriClaim.claim.offeredBy?.identifier &&
|
||||||
@@ -120,7 +121,7 @@ export const offerGiverDid: (
|
|||||||
* @returns true if the user can fulfill the offer
|
* @returns true if the user can fulfill the offer
|
||||||
* @param veriClaim is expected to have fields: claim, claimType, and issuer
|
* @param veriClaim is expected to have fields: claim, claimType, and issuer
|
||||||
*/
|
*/
|
||||||
export const canFulfillOffer = (veriClaim: GenericServerRecord) => {
|
export const canFulfillOffer = (veriClaim: GenericCredWrapper) => {
|
||||||
return !!(veriClaim.claimType === "Offer" && offerGiverDid(veriClaim));
|
return !!(veriClaim.claimType === "Offer" && offerGiverDid(veriClaim));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -237,7 +238,7 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const sendTestThroughPushServer = async (
|
export const sendTestThroughPushServer = async (
|
||||||
subscription: PushSubscription,
|
subscriptionJSON: PushSubscriptionJSON,
|
||||||
skipFilter: boolean,
|
skipFilter: boolean,
|
||||||
): Promise<AxiosResponse> => {
|
): Promise<AxiosResponse> => {
|
||||||
await db.open();
|
await db.open();
|
||||||
@@ -252,28 +253,11 @@ export const sendTestThroughPushServer = async (
|
|||||||
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
|
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
|
||||||
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
|
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
|
||||||
|
|
||||||
const auth = Buffer.from(subscription.getKey("auth"));
|
|
||||||
const authB64 = auth
|
|
||||||
.toString("base64")
|
|
||||||
.replace(/\+/g, "-")
|
|
||||||
.replace(/\//g, "_")
|
|
||||||
.replace(/=+$/, "");
|
|
||||||
const p256dh = Buffer.from(subscription.getKey("p256dh"));
|
|
||||||
const p256dhB64 = p256dh
|
|
||||||
.toString("base64")
|
|
||||||
.replace(/\+/g, "-")
|
|
||||||
.replace(/\//g, "_")
|
|
||||||
.replace(/=+$/, "");
|
|
||||||
const newPayload = {
|
const newPayload = {
|
||||||
endpoint: subscription.endpoint,
|
// eslint-disable-next-line prettier/prettier
|
||||||
keys: {
|
message: `Test, where you will see this message ${ skipFilter ? "un" : "" }filtered.`,
|
||||||
auth: authB64,
|
|
||||||
p256dh: p256dhB64,
|
|
||||||
},
|
|
||||||
message: `Test, where you will see this message ${
|
|
||||||
skipFilter ? "un" : ""
|
|
||||||
}filtered.`,
|
|
||||||
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
|
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
|
||||||
|
...subscriptionJSON,
|
||||||
};
|
};
|
||||||
console.log("Sending a test web push message:", newPayload);
|
console.log("Sending a test web push message:", newPayload);
|
||||||
const payloadStr = JSON.stringify(newPayload);
|
const payloadStr = JSON.stringify(newPayload);
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ import {
|
|||||||
faCalendar,
|
faCalendar,
|
||||||
faCamera,
|
faCamera,
|
||||||
faCheck,
|
faCheck,
|
||||||
|
faChevronDown,
|
||||||
faChevronLeft,
|
faChevronLeft,
|
||||||
faChevronRight,
|
faChevronRight,
|
||||||
|
faChevronUp,
|
||||||
faCircle,
|
faCircle,
|
||||||
faCircleCheck,
|
faCircleCheck,
|
||||||
faCircleInfo,
|
faCircleInfo,
|
||||||
@@ -81,8 +83,10 @@ library.add(
|
|||||||
faCalendar,
|
faCalendar,
|
||||||
faCamera,
|
faCamera,
|
||||||
faCheck,
|
faCheck,
|
||||||
|
faChevronDown,
|
||||||
faChevronLeft,
|
faChevronLeft,
|
||||||
faChevronRight,
|
faChevronRight,
|
||||||
|
faChevronUp,
|
||||||
faCircle,
|
faCircle,
|
||||||
faCircleCheck,
|
faCircleCheck,
|
||||||
faCircleInfo,
|
faCircleInfo,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { register } from "register-service-worker";
|
import { register } from "register-service-worker";
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (import.meta.env.NODE_ENV === "production") {
|
||||||
register("/sw_scripts-combined.js", {
|
register("/sw_scripts-combined.js", {
|
||||||
ready() {
|
ready() {
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
@@ -31,229 +31,159 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "/account",
|
path: "/account",
|
||||||
name: "account",
|
name: "account",
|
||||||
component: () =>
|
component: () => import("../views/AccountViewView.vue"),
|
||||||
import(/* webpackChunkName: "account" */ "../views/AccountViewView.vue"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/claim/:id?",
|
path: "/claim/:id?",
|
||||||
name: "claim",
|
name: "claim",
|
||||||
component: () =>
|
component: () => import("../views/ClaimView.vue"),
|
||||||
import(/* webpackChunkName: "claim" */ "../views/ClaimView.vue"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/confirm-contact",
|
path: "/confirm-contact",
|
||||||
name: "confirm-contact",
|
name: "confirm-contact",
|
||||||
component: () =>
|
component: () => import("../views/ConfirmContactView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "confirm-contact" */ "../views/ConfirmContactView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/contact-amounts",
|
path: "/contact-amounts",
|
||||||
name: "contact-amounts",
|
name: "contact-amounts",
|
||||||
component: () =>
|
component: () => import("../views/ContactAmountsView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "contact-amounts" */ "../views/ContactAmountsView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/contact-gives",
|
path: "/contact-gives",
|
||||||
name: "contact-gives",
|
name: "contact-gives",
|
||||||
component: () =>
|
component: () => import("../views/ContactGiftingView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "contact-gives" */ "../views/ContactGiftingView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/contact-qr",
|
path: "/contact-qr",
|
||||||
name: "contact-qr",
|
name: "contact-qr",
|
||||||
component: () =>
|
component: () => import("../views/ContactQRScanShowView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "contact-qr" */ "../views/ContactQRScanShowView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/contacts",
|
path: "/contacts",
|
||||||
name: "contacts",
|
name: "contacts",
|
||||||
component: () =>
|
component: () => import("../views/ContactsView.vue"),
|
||||||
import(/* webpackChunkName: "contacts" */ "../views/ContactsView.vue"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/discover",
|
path: "/discover",
|
||||||
name: "discover",
|
name: "discover",
|
||||||
component: () =>
|
component: () => import("../views/DiscoverView.vue"),
|
||||||
import(/* webpackChunkName: "discover" */ "../views/DiscoverView.vue"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/gifted-details",
|
path: "/gifted-details",
|
||||||
name: "gifted-details",
|
name: "gifted-details",
|
||||||
component: () =>
|
component: () => import("../views/GiftedDetails.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "gifted-details" */ "../views/GiftedDetails.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/help",
|
path: "/help",
|
||||||
name: "help",
|
name: "help",
|
||||||
component: () =>
|
component: () => import("../views/HelpView.vue"),
|
||||||
import(/* webpackChunkName: "help" */ "../views/HelpView.vue"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/help-notifications",
|
path: "/help-notifications",
|
||||||
name: "help-notifications",
|
name: "help-notifications",
|
||||||
component: () =>
|
component: () => import("../views/HelpNotificationsView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "help-notifications" */ "../views/HelpNotificationsView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/help-onboarding",
|
path: "/help-onboarding",
|
||||||
name: "help-onboarding",
|
name: "help-onboarding",
|
||||||
component: () =>
|
component: () => import("../views/HelpOnboardingView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "help-onboarding" */ "../views/HelpOnboardingView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
name: "home",
|
name: "home",
|
||||||
component: () =>
|
component: () => import("../views/HomeView.vue"),
|
||||||
import(/* webpackChunkName: "home" */ "../views/HomeView.vue"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/identity-switcher",
|
path: "/identity-switcher",
|
||||||
name: "identity-switcher",
|
name: "identity-switcher",
|
||||||
component: () =>
|
component: () => import("../views/IdentitySwitcherView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "identity-switcher" */ "../views/IdentitySwitcherView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/import-account",
|
path: "/import-account",
|
||||||
name: "import-account",
|
name: "import-account",
|
||||||
component: () =>
|
component: () => import("../views/ImportAccountView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "import-account" */ "../views/ImportAccountView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/import-derive",
|
path: "/import-derive",
|
||||||
name: "import-derive",
|
name: "import-derive",
|
||||||
component: () =>
|
component: () => import("../views/ImportDerivedAccountView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "import-derive" */ "../views/ImportDerivedAccountView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/new-edit-account",
|
path: "/new-edit-account",
|
||||||
name: "new-edit-account",
|
name: "new-edit-account",
|
||||||
component: () =>
|
component: () => import("../views/NewEditAccountView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "new-edit-account" */ "../views/NewEditAccountView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/new-edit-project",
|
path: "/new-edit-project",
|
||||||
name: "new-edit-project",
|
name: "new-edit-project",
|
||||||
component: () =>
|
component: () => import("../views/NewEditProjectView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "new-edit-project" */ "../views/NewEditProjectView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/new-identifier",
|
path: "/new-identifier",
|
||||||
name: "new-identifier",
|
name: "new-identifier",
|
||||||
component: () =>
|
component: () => import("../views/NewIdentifierView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "new-identifier" */ "../views/NewIdentifierView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/project/:id?",
|
path: "/project/:id?",
|
||||||
name: "project",
|
name: "project",
|
||||||
component: () =>
|
component: () => import("../views/ProjectViewView.vue"),
|
||||||
import(/* webpackChunkName: "project" */ "../views/ProjectViewView.vue"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/projects",
|
path: "/projects",
|
||||||
name: "projects",
|
name: "projects",
|
||||||
component: () =>
|
component: () => import("../views/ProjectsView.vue"),
|
||||||
import(/* webpackChunkName: "projects" */ "../views/ProjectsView.vue"),
|
|
||||||
beforeEnter: enterOrStart,
|
beforeEnter: enterOrStart,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/quick-action-bvc",
|
path: "/quick-action-bvc",
|
||||||
name: "quick-action-bvc",
|
name: "quick-action-bvc",
|
||||||
component: () =>
|
component: () => import("../views/QuickActionBvcView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "quick-action-bvc" */ "../views/QuickActionBvcView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/quick-action-bvc-begin",
|
path: "/quick-action-bvc-begin",
|
||||||
name: "quick-action-bvc-begin",
|
name: "quick-action-bvc-begin",
|
||||||
component: () =>
|
component: () => import("../views/QuickActionBvcBeginView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "quick-action-bvc-begin" */ "../views/QuickActionBvcBeginView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/quick-action-bvc-end",
|
path: "/quick-action-bvc-end",
|
||||||
name: "quick-action-bvc-end",
|
name: "quick-action-bvc-end",
|
||||||
component: () =>
|
component: () => import("../views/QuickActionBvcEndView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "quick-action-bvc-end" */ "../views/QuickActionBvcEndView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/scan-contact",
|
path: "/scan-contact",
|
||||||
name: "scan-contact",
|
name: "scan-contact",
|
||||||
component: () =>
|
component: () => import("../views/ContactScanView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "scan-contact" */ "../views/ContactScanView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/search-area",
|
path: "/search-area",
|
||||||
name: "search-area",
|
name: "search-area",
|
||||||
component: () =>
|
component: () => import("../views/SearchAreaView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "search-area" */ "../views/SearchAreaView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/seed-backup",
|
path: "/seed-backup",
|
||||||
name: "seed-backup",
|
name: "seed-backup",
|
||||||
component: () =>
|
component: () => import("../views/SeedBackupView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "seed-backup" */ "../views/SeedBackupView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/start",
|
path: "/start",
|
||||||
name: "start",
|
name: "start",
|
||||||
component: () =>
|
component: () => import("../views/StartView.vue"),
|
||||||
import(/* webpackChunkName: "start" */ "../views/StartView.vue"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/statistics",
|
path: "/statistics",
|
||||||
name: "statistics",
|
name: "statistics",
|
||||||
component: () =>
|
component: () => import("../views/StatisticsView.vue"),
|
||||||
import(
|
|
||||||
/* webpackChunkName: "statistics" */ "../views/StatisticsView.vue"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/test",
|
path: "/test",
|
||||||
name: "test",
|
name: "test",
|
||||||
component: () =>
|
component: () => import("../views/TestView.vue"),
|
||||||
import(/* webpackChunkName: "test" */ "../views/TestView.vue"),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @type {*} */
|
/** @type {*} */
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(process.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
1
src/util.d.ts
vendored
1
src/util.d.ts
vendored
@@ -1,4 +1,5 @@
|
|||||||
// from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/util.d.ts
|
// from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/util.d.ts
|
||||||
|
/* eslint-disable */
|
||||||
/**
|
/**
|
||||||
* The `node:util` module supports the needs of Node.js internal APIs. Many of the
|
* The `node:util` module supports the needs of Node.js internal APIs. Many of the
|
||||||
* utilities are useful for application and module developers as well. To access
|
* utilities are useful for application and module developers as well. To access
|
||||||
|
|||||||
@@ -112,6 +112,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
|
||||||
|
<!-- label -->
|
||||||
|
<div class="mb-2 font-bold">Settings</div>
|
||||||
<div
|
<div
|
||||||
v-if="!notificationMaybeChanged"
|
v-if="!notificationMaybeChanged"
|
||||||
class="flex items-center justify-between cursor-pointer"
|
class="flex items-center justify-between cursor-pointer"
|
||||||
@@ -140,16 +142,38 @@
|
|||||||
Notification status may have changed. Refresh this page to see the
|
Notification status may have changed. Refresh this page to see the
|
||||||
latest setting.
|
latest setting.
|
||||||
</div>
|
</div>
|
||||||
<router-link class="px-4 text-sm text-blue-500" to="/help-notifications">
|
<router-link class="pl-4 text-sm text-blue-500" to="/help-notifications">
|
||||||
Troubleshoot your notification setup.
|
Troubleshoot your notification setup.
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'search-area' }"
|
||||||
|
v-if="activeDid"
|
||||||
|
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-6"
|
||||||
|
>
|
||||||
|
Set Search Area…
|
||||||
|
<!-- If already set, change button label to "Change Search Area" -->
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<div class="text-slate-500 text-sm font-bold mt-6 mb-2">
|
||||||
|
Topics of Interest
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
class="block w-full rounded border border-slate-400 px-3 py-2"
|
||||||
|
rows="3"
|
||||||
|
value="longing, rusted, seventeen, daybreak, furnace, nine, benign, homecoming, one, freight car"
|
||||||
|
>
|
||||||
|
</textarea>
|
||||||
|
<div class="text-slate-500 text-sm mt-2 mb-2">
|
||||||
|
Separate topics with a comma.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="activeDid"
|
v-if="activeDid"
|
||||||
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
||||||
>
|
>
|
||||||
<div class="mb-2">Usage Limits</div>
|
<div class="mb-2 font-bold">Usage Limits</div>
|
||||||
<!-- show spinner if loading limits -->
|
<!-- show spinner if loading limits -->
|
||||||
<div v-if="loadingLimits" class="text-center">
|
<div v-if="loadingLimits" class="text-center">
|
||||||
Checking… <fa icon="spinner" class="fa-spin"></fa>
|
Checking… <fa icon="spinner" class="fa-spin"></fa>
|
||||||
@@ -200,7 +224,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
|
||||||
<div>Data Export</div>
|
<div class="mb-2 font-bold">Data Export</div>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'seed-backup' }"
|
:to="{ name: 'seed-backup' }"
|
||||||
v-if="activeDid"
|
v-if="activeDid"
|
||||||
@@ -211,7 +235,7 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
v-bind:class="computedStartDownloadLinkClassNames()"
|
v-bind:class="computedStartDownloadLinkClassNames()"
|
||||||
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
@click="exportDatabase()"
|
@click="exportDatabase()"
|
||||||
>
|
>
|
||||||
Download Settings & Contacts
|
Download Settings & Contacts
|
||||||
@@ -221,7 +245,7 @@
|
|||||||
<a
|
<a
|
||||||
ref="downloadLink"
|
ref="downloadLink"
|
||||||
v-bind:class="computedDownloadLinkClassNames()"
|
v-bind:class="computedDownloadLinkClassNames()"
|
||||||
class="block w-full text-center text-md uppercase bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
class="block w-full text-center text-md uppercase bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
||||||
>
|
>
|
||||||
If no download happened yet, click again here to download now.
|
If no download happened yet, click again here to download now.
|
||||||
</a>
|
</a>
|
||||||
@@ -539,9 +563,7 @@ import {
|
|||||||
EndorserRateLimits,
|
EndorserRateLimits,
|
||||||
ImageRateLimits,
|
ImageRateLimits,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
|
import { Buffer } from "buffer/";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const Buffer = require("buffer/").Buffer;
|
|
||||||
|
|
||||||
interface IAccount {
|
interface IAccount {
|
||||||
did: string;
|
did: string;
|
||||||
@@ -952,13 +974,13 @@ export default class AccountViewView extends Vue {
|
|||||||
|
|
||||||
public computedStartDownloadLinkClassNames() {
|
public computedStartDownloadLinkClassNames() {
|
||||||
return {
|
return {
|
||||||
invisible: this.downloadUrl,
|
hidden: this.downloadUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public computedDownloadLinkClassNames() {
|
public computedDownloadLinkClassNames() {
|
||||||
return {
|
return {
|
||||||
invisible: !this.downloadUrl,
|
hidden: !this.downloadUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1146,9 +1168,9 @@ export default class AccountViewView extends Vue {
|
|||||||
this.limitsMessage =
|
this.limitsMessage =
|
||||||
(data?.error?.message as string) || "Bad server response.";
|
(data?.error?.message as string) || "Bad server response.";
|
||||||
console.error(
|
console.error(
|
||||||
"Got bad response retrieving limits, which usually means user isn't registered:",
|
"Got bad response retrieving limits, which usually means user isn't registered.",
|
||||||
error,
|
|
||||||
);
|
);
|
||||||
|
//console.error(error);
|
||||||
} else {
|
} else {
|
||||||
this.limitsMessage = "Got an error retrieving limits.";
|
this.limitsMessage = "Got an error retrieving limits.";
|
||||||
console.error("Got some error retrieving limits:", error);
|
console.error("Got some error retrieving limits:", error);
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
|||||||
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||||
import {
|
import {
|
||||||
AgreeVerifiableCredential,
|
AgreeVerifiableCredential,
|
||||||
GiveServerRecord,
|
GiveSummaryRecord,
|
||||||
GiveVerifiableCredential,
|
GiveVerifiableCredential,
|
||||||
SCHEMA_ORG_CONTEXT,
|
SCHEMA_ORG_CONTEXT,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
@@ -131,7 +131,7 @@ export default class ContactAmountssView extends Vue {
|
|||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
contact: Contact | null = null;
|
contact: Contact | null = null;
|
||||||
giveRecords: Array<GiveServerRecord> = [];
|
giveRecords: Array<GiveSummaryRecord> = [];
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
|
|
||||||
async beforeCreate() {
|
async beforeCreate() {
|
||||||
@@ -197,7 +197,7 @@ export default class ContactAmountssView extends Vue {
|
|||||||
async loadGives(activeDid: string, contact: Contact) {
|
async loadGives(activeDid: string, contact: Contact) {
|
||||||
try {
|
try {
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
let result: Array<GiveServerRecord> = [];
|
let result: Array<GiveSummaryRecord> = [];
|
||||||
const url =
|
const url =
|
||||||
this.apiServer +
|
this.apiServer +
|
||||||
"/api/v2/report/gives?agentDid=" +
|
"/api/v2/report/gives?agentDid=" +
|
||||||
@@ -252,7 +252,7 @@ export default class ContactAmountssView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedResult: Array<GiveServerRecord> = R.sort(
|
const sortedResult: Array<GiveSummaryRecord> = R.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
new Date(b.issuedAt).getTime() - new Date(a.issuedAt).getTime(),
|
new Date(b.issuedAt).getTime() - new Date(a.issuedAt).getTime(),
|
||||||
result,
|
result,
|
||||||
@@ -271,7 +271,7 @@ export default class ContactAmountssView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirm(record: GiveServerRecord) {
|
async confirm(record: GiveSummaryRecord) {
|
||||||
// Make claim
|
// Make claim
|
||||||
// I use clone here because otherwise it gets a Proxy object.
|
// I use clone here because otherwise it gets a Proxy object.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
@@ -88,9 +88,7 @@ import {
|
|||||||
CONTACT_URL_PREFIX,
|
CONTACT_URL_PREFIX,
|
||||||
ENDORSER_JWT_URL_LOCATION,
|
ENDORSER_JWT_URL_LOCATION,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
|
import { Buffer } from "buffer/";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const Buffer = require("buffer/").Buffer;
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
CONTACT_CSV_HEADER,
|
CONTACT_CSV_HEADER,
|
||||||
CONTACT_URL_PREFIX,
|
CONTACT_URL_PREFIX,
|
||||||
GiveServerRecord,
|
GiveSummaryRecord,
|
||||||
GiveVerifiableCredential,
|
GiveVerifiableCredential,
|
||||||
isDid,
|
isDid,
|
||||||
RegisterVerifiableCredential,
|
RegisterVerifiableCredential,
|
||||||
@@ -314,8 +314,7 @@ 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";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
import { Buffer } from "buffer/";
|
||||||
const Buffer = require("buffer/").Buffer;
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { QuickNav, EntityIcon },
|
components: { QuickNav, EntityIcon },
|
||||||
@@ -410,7 +409,7 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleResponse = (
|
const handleResponse = (
|
||||||
resp: { status: number; data: { data: GiveServerRecord[] } },
|
resp: { status: number; data: { data: GiveSummaryRecord[] } },
|
||||||
descriptions: Record<string, string>,
|
descriptions: Record<string, string>,
|
||||||
confirmed: Record<string, number>,
|
confirmed: Record<string, number>,
|
||||||
unconfirmed: Record<string, number>,
|
unconfirmed: Record<string, number>,
|
||||||
|
|||||||
@@ -301,12 +301,13 @@ import { sendTestThroughPushServer } from "@/libs/util";
|
|||||||
export default class HelpNotificationsView extends Vue {
|
export default class HelpNotificationsView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
subscription: PushSubscription | null = null;
|
subscriptionJSON?: PushSubscriptionJSON;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
const registration = await navigator.serviceWorker.ready;
|
const registration = await navigator.serviceWorker.ready;
|
||||||
this.subscription = await registration.pushManager.getSubscription();
|
const fullSub = await registration.pushManager.getSubscription();
|
||||||
|
this.subscriptionJSON = fullSub?.toJSON();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Mount error:", error);
|
console.error("Mount error:", error);
|
||||||
}
|
}
|
||||||
@@ -315,13 +316,13 @@ export default class HelpNotificationsView extends Vue {
|
|||||||
alertWebPushSubscription() {
|
alertWebPushSubscription() {
|
||||||
console.log(
|
console.log(
|
||||||
"Web push subscription:",
|
"Web push subscription:",
|
||||||
JSON.parse(JSON.stringify(this.subscription)), // gives more info than plain console logging
|
JSON.parse(JSON.stringify(this.subscriptionJSON)), // gives more info than plain console logging
|
||||||
);
|
);
|
||||||
alert(JSON.stringify(this.subscription));
|
alert(JSON.stringify(this.subscriptionJSON));
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendTestWebPushMessage(skipFilter: boolean = false) {
|
async sendTestWebPushMessage(skipFilter: boolean = false) {
|
||||||
if (!this.subscription) {
|
if (!this.subscriptionJSON) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -336,7 +337,7 @@ export default class HelpNotificationsView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendTestThroughPushServer(this.subscription, skipFilter);
|
await sendTestThroughPushServer(this.subscriptionJSON, skipFilter);
|
||||||
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -382,6 +382,6 @@ export default class Help extends Vue {
|
|||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
package = Package;
|
package = Package;
|
||||||
commitHash = process.env.VUE_APP_GIT_HASH;
|
commitHash = import.meta.env.VITE_GIT_HASH;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -174,10 +174,29 @@
|
|||||||
showGivenToUser="true"
|
showGivenToUser="true"
|
||||||
/>
|
/>
|
||||||
<GiftedPrompts ref="giftedPrompts" />
|
<GiftedPrompts ref="giftedPrompts" />
|
||||||
|
<FeedFilters ref="feedFilters" />
|
||||||
|
|
||||||
<!-- Results List -->
|
<!-- Results List -->
|
||||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||||
<h2 class="text-xl font-bold mb-4">Latest Activity</h2>
|
<div class="flex items-center mb-4">
|
||||||
|
<h2 class="text-xl font-bold">Latest Activity</h2>
|
||||||
|
<button @click="openFeedFilters()" class="block text-center ml-auto">
|
||||||
|
<span class="text-sm uppercase text-white">
|
||||||
|
<span
|
||||||
|
v-if="resultsAreFiltered()"
|
||||||
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md"
|
||||||
|
>
|
||||||
|
Filtered
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md"
|
||||||
|
>
|
||||||
|
Unfiltered
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<InfiniteScroll @reached-bottom="loadMoreGives">
|
<InfiniteScroll @reached-bottom="loadMoreGives">
|
||||||
<ul class="border-t border-slate-300">
|
<ul class="border-t border-slate-300">
|
||||||
<li
|
<li
|
||||||
@@ -193,20 +212,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-12">
|
<div class="grid grid-cols-12">
|
||||||
<span class="col-span-11 justify-self-start">
|
<span class="col-span-1 justify-self-start">
|
||||||
<span>
|
<span>
|
||||||
<fa
|
<fa
|
||||||
v-if="record.giver.known || record.receiver.known"
|
v-if="record.giver.known || record.receiver.known"
|
||||||
icon="circle-user"
|
icon="circle-user"
|
||||||
class="col-span-1 pt-1 pl-0 pr-3 text-slate-500"
|
class="pt-1 text-slate-500"
|
||||||
/>
|
|
||||||
<fa
|
|
||||||
v-else
|
|
||||||
icon="gift"
|
|
||||||
class="col-span-1 pt-1 pl-3 pr-0 text-slate-500"
|
|
||||||
/>
|
/>
|
||||||
|
<fa v-else icon="gift" class="pt-1 pl-3 text-slate-500" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="col-span-10 justify-self-stretch">
|
||||||
|
<span class="pl-2">
|
||||||
|
{{ giveDescription(record) }}
|
||||||
</span>
|
</span>
|
||||||
{{ giveDescription(record) }}
|
|
||||||
<a @click="onClickLoadClaim(record.jwtId)">
|
<a @click="onClickLoadClaim(record.jwtId)">
|
||||||
<fa
|
<fa
|
||||||
icon="file-lines"
|
icon="file-lines"
|
||||||
@@ -214,16 +233,15 @@
|
|||||||
></fa>
|
></fa>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="col-span-1 justify-self-end shrink">
|
<span class="col-span-1 justify-self-end">
|
||||||
<router-link
|
<router-link
|
||||||
v-if="record.fulfillsPlanHandleId"
|
v-if="record.fulfillsPlanHandleId"
|
||||||
:to="
|
:to="
|
||||||
'/project/' +
|
'/project/' +
|
||||||
encodeURIComponent(record.fulfillsPlanHandleId)
|
encodeURIComponent(record.fulfillsPlanHandleId)
|
||||||
"
|
"
|
||||||
class="justify-end"
|
|
||||||
>
|
>
|
||||||
<fa icon="hammer" class="ml-4 pl-2 text-blue-500"></fa>
|
<fa icon="hammer" class="text-blue-500"></fa>
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,11 +258,17 @@
|
|||||||
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading…
|
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading…
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!isFeedLoading && feedData.length === 0">
|
||||||
|
<p class="text-slate-500 text-center italic mt-4 mb-4">
|
||||||
|
No claims match your filters.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import * as R from "ramda";
|
||||||
import { UAParser } from "ua-parser-js";
|
import { UAParser } from "ua-parser-js";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
@@ -252,6 +276,7 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import GiftedPrompts from "@/components/GiftedPrompts.vue";
|
import GiftedPrompts from "@/components/GiftedPrompts.vue";
|
||||||
|
import FeedFilters from "@/components/FeedFilters.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";
|
||||||
@@ -259,22 +284,30 @@ 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";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import {
|
||||||
|
BoundingBox,
|
||||||
|
isAnyFeedFilterOn,
|
||||||
|
MASTER_SETTINGS_KEY,
|
||||||
|
Settings,
|
||||||
|
} from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import {
|
import {
|
||||||
contactForDid,
|
contactForDid,
|
||||||
|
containsNonHiddenDid,
|
||||||
didInfoForContact,
|
didInfoForContact,
|
||||||
|
getPlanFromCache,
|
||||||
GiverInputInfo,
|
GiverInputInfo,
|
||||||
GiveServerRecord,
|
GiveSummaryRecord,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import { generateSaveAndActivateIdentity } from "@/libs/util";
|
import { generateSaveAndActivateIdentity } from "@/libs/util";
|
||||||
|
|
||||||
interface GiveRecordWithContactInfo extends GiveServerRecord {
|
interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
||||||
giver: {
|
giver: {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
known: boolean;
|
known: boolean;
|
||||||
};
|
};
|
||||||
image: string;
|
image: string;
|
||||||
|
recipientProjectName: string | undefined;
|
||||||
receiver: {
|
receiver: {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
known: boolean;
|
known: boolean;
|
||||||
@@ -285,6 +318,7 @@ interface GiveRecordWithContactInfo extends GiveServerRecord {
|
|||||||
components: {
|
components: {
|
||||||
GiftedDialog,
|
GiftedDialog,
|
||||||
GiftedPrompts,
|
GiftedPrompts,
|
||||||
|
FeedFilters,
|
||||||
QuickNav,
|
QuickNav,
|
||||||
EntityIcon,
|
EntityIcon,
|
||||||
InfiniteScroll,
|
InfiniteScroll,
|
||||||
@@ -301,9 +335,16 @@ export default class HomeView extends Vue {
|
|||||||
feedData: GiveRecordWithContactInfo[] = [];
|
feedData: GiveRecordWithContactInfo[] = [];
|
||||||
feedPreviousOldestId?: string;
|
feedPreviousOldestId?: string;
|
||||||
feedLastViewedClaimId?: string;
|
feedLastViewedClaimId?: string;
|
||||||
|
isAnyFeedFilterOn: boolean;
|
||||||
isCreatingIdentifier = false;
|
isCreatingIdentifier = false;
|
||||||
|
isFeedFilteredByVisible = false;
|
||||||
|
isFeedFilteredByNearby = false;
|
||||||
isFeedLoading = true;
|
isFeedLoading = true;
|
||||||
isRegistered = false;
|
isRegistered = false;
|
||||||
|
searchBoxes: Array<{
|
||||||
|
name: string;
|
||||||
|
bbox: BoundingBox;
|
||||||
|
}> = [];
|
||||||
showShortcutBvc = 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
|
||||||
|
|
||||||
@@ -326,7 +367,7 @@ export default class HomeView extends Vue {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
async created() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const allAccounts = await accountsDB.accounts.toArray();
|
const allAccounts = await accountsDB.accounts.toArray();
|
||||||
@@ -338,9 +379,14 @@ export default class HomeView extends Vue {
|
|||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await db.contacts.toArray();
|
||||||
this.feedLastViewedClaimId = settings?.lastViewedClaimId;
|
this.feedLastViewedClaimId = settings?.lastViewedClaimId;
|
||||||
|
this.isFeedFilteredByVisible = !!settings?.filterFeedByVisible;
|
||||||
|
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
this.searchBoxes = settings?.searchBoxes || [];
|
||||||
this.showShortcutBvc = !!settings?.showShortcutBvc;
|
this.showShortcutBvc = !!settings?.showShortcutBvc;
|
||||||
|
|
||||||
|
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
|
||||||
|
|
||||||
if (this.allMyDids.length === 0) {
|
if (this.allMyDids.length === 0) {
|
||||||
this.isCreatingIdentifier = true;
|
this.isCreatingIdentifier = true;
|
||||||
this.activeDid = await generateSaveAndActivateIdentity();
|
this.activeDid = await generateSaveAndActivateIdentity();
|
||||||
@@ -369,6 +415,10 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resultsAreFiltered() {
|
||||||
|
return this.isFeedFilteredByVisible || this.isFeedFilteredByNearby;
|
||||||
|
}
|
||||||
|
|
||||||
notificationsSupported() {
|
notificationsSupported() {
|
||||||
return "Notification" in window;
|
return "Notification" in window;
|
||||||
}
|
}
|
||||||
@@ -378,27 +428,34 @@ export default class HomeView extends Vue {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
if (this.activeDid) {
|
if (this.activeDid) {
|
||||||
await accountsDB.open();
|
if (identity) {
|
||||||
const allAccounts = await accountsDB.accounts.toArray();
|
headers["Authorization"] = "Bearer " + (await accessToken(identity));
|
||||||
const account = allAccounts.find(
|
} else {
|
||||||
(acc) => acc.did === this.activeDid,
|
|
||||||
) as Account;
|
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
|
||||||
|
|
||||||
if (!identity) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service. Switch your ID.",
|
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service. Switch your ID.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
headers["Authorization"] = "Bearer " + (await accessToken(identity));
|
|
||||||
} else {
|
} else {
|
||||||
// it's OK without auth... we just won't get any identifiers
|
// it's OK without auth... we just won't get any identifiers
|
||||||
}
|
}
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only called when a setting was changed
|
||||||
|
async reloadFeedOnChange() {
|
||||||
|
await db.open();
|
||||||
|
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
||||||
|
this.isFeedFilteredByVisible = !!settings?.filterFeedByVisible;
|
||||||
|
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
|
||||||
|
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
|
||||||
|
|
||||||
|
this.feedData = [];
|
||||||
|
this.feedPreviousOldestId = undefined;
|
||||||
|
this.updateAllFeed();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data loader used by infinite scroller
|
* Data loader used by infinite scroller
|
||||||
* @param payload is the flag from the InfiniteScroll indicating if it should load
|
* @param payload is the flag from the InfiniteScroll indicating if it should load
|
||||||
@@ -409,14 +466,30 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
latLongInAnySearchBox(lat: number, long: number) {
|
||||||
|
for (const boxInfo of this.searchBoxes) {
|
||||||
|
if (
|
||||||
|
boxInfo.bbox.westLong <= long &&
|
||||||
|
long <= boxInfo.bbox.eastLong &&
|
||||||
|
boxInfo.bbox.minLat <= lat &&
|
||||||
|
lat <= boxInfo.bbox.maxLat
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async updateAllFeed() {
|
public async updateAllFeed() {
|
||||||
this.isFeedLoading = true;
|
this.isFeedLoading = true;
|
||||||
|
let endOfResults = true;
|
||||||
await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
|
await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
|
||||||
.then(async (results) => {
|
.then(async (results) => {
|
||||||
if (results.data.length > 0) {
|
if (results.data.length > 0) {
|
||||||
|
endOfResults = false;
|
||||||
// include the descriptions of the giver and receiver
|
// include the descriptions of the giver and receiver
|
||||||
const newFeedData: GiveRecordWithContactInfo = results.data.map(
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
(record: GiveServerRecord) => {
|
const newFeedData: Array<Promise<GiveRecordWithContactInfo>> =
|
||||||
|
results.data.map(async (record: GiveSummaryRecord) => {
|
||||||
// similar code is in endorser-mobile utility.ts
|
// similar code is in endorser-mobile utility.ts
|
||||||
// claim.claim happen for some claims wrapped in a Verifiable Credential
|
// claim.claim happen for some claims wrapped in a Verifiable Credential
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -427,6 +500,36 @@ export default class HomeView extends Vue {
|
|||||||
// recipient.did is for legacy data, before March 2023
|
// recipient.did is for legacy data, before March 2023
|
||||||
const recipientDid =
|
const recipientDid =
|
||||||
claim.recipient?.identifier || (claim.recipient as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
|
claim.recipient?.identifier || (claim.recipient as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
const plan = await getPlanFromCache(
|
||||||
|
record.fulfillsPlanHandleId,
|
||||||
|
identity,
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
);
|
||||||
|
|
||||||
|
// check if the record should be filtered out
|
||||||
|
let anyMatch = false;
|
||||||
|
if (
|
||||||
|
this.isFeedFilteredByVisible &&
|
||||||
|
containsNonHiddenDid(record)
|
||||||
|
) {
|
||||||
|
// has a visible DID so it's a keeper
|
||||||
|
anyMatch = true;
|
||||||
|
}
|
||||||
|
if (!anyMatch && this.isFeedFilteredByNearby) {
|
||||||
|
// check if the associated project has a location inside user's search box
|
||||||
|
if (record.fulfillsPlanHandleId) {
|
||||||
|
if (plan?.locLat && plan?.locLon) {
|
||||||
|
if (this.latLongInAnySearchBox(plan.locLat, plan.locLon)) {
|
||||||
|
anyMatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.isAnyFeedFilterOn && !anyMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...record,
|
...record,
|
||||||
giver: didInfoForContact(
|
giver: didInfoForContact(
|
||||||
@@ -436,6 +539,7 @@ export default class HomeView extends Vue {
|
|||||||
this.allMyDids,
|
this.allMyDids,
|
||||||
),
|
),
|
||||||
image: claim.image,
|
image: claim.image,
|
||||||
|
recipientProjectName: plan?.name,
|
||||||
receiver: didInfoForContact(
|
receiver: didInfoForContact(
|
||||||
recipientDid,
|
recipientDid,
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
@@ -443,9 +547,11 @@ export default class HomeView extends Vue {
|
|||||||
this.allMyDids,
|
this.allMyDids,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
);
|
const allNewFeedData: GiveRecordWithContactInfo[] =
|
||||||
this.feedData = this.feedData.concat(newFeedData);
|
await Promise.all(newFeedData);
|
||||||
|
const filteredFeedData = allNewFeedData.filter(R.isNotNil);
|
||||||
|
this.feedData = this.feedData.concat(filteredFeedData);
|
||||||
this.feedPreviousOldestId =
|
this.feedPreviousOldestId =
|
||||||
results.data[results.data.length - 1].jwtId;
|
results.data[results.data.length - 1].jwtId;
|
||||||
// The following update is only done on the first load.
|
// The following update is only done on the first load.
|
||||||
@@ -472,6 +578,10 @@ export default class HomeView extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
if (this.feedData.length === 0 && !endOfResults) {
|
||||||
|
// repeat until there's at least some data
|
||||||
|
this.updateAllFeed();
|
||||||
|
}
|
||||||
this.isFeedLoading = false;
|
this.isFeedLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,12 +648,28 @@ export default class HomeView extends Vue {
|
|||||||
return `${giverInfo.displayName} gave to ${recipientInfo.displayName}: ${gaveAmount}`;
|
return `${giverInfo.displayName} gave to ${recipientInfo.displayName}: ${gaveAmount}`;
|
||||||
} else if (giverInfo.known) {
|
} else if (giverInfo.known) {
|
||||||
// giver is named but recipient is not
|
// giver is named but recipient is not
|
||||||
|
|
||||||
|
// show the project name if to one
|
||||||
|
if (giveRecord.recipientProjectName) {
|
||||||
|
// retrieve the project name
|
||||||
|
return `${giverInfo.displayName} gave: ${gaveAmount} (to the project ${giveRecord.recipientProjectName})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's not to a project
|
||||||
return `${giverInfo.displayName} gave: ${gaveAmount} (to ${recipientInfo.displayName})`;
|
return `${giverInfo.displayName} gave: ${gaveAmount} (to ${recipientInfo.displayName})`;
|
||||||
} else if (recipientInfo.known) {
|
} else if (recipientInfo.known) {
|
||||||
// recipient is named but giver is not
|
// recipient is named but giver is not
|
||||||
return `${recipientInfo.displayName} received: ${gaveAmount} (from ${giverInfo.displayName})`;
|
return `${recipientInfo.displayName} received: ${gaveAmount} (from ${giverInfo.displayName})`;
|
||||||
} else {
|
} else {
|
||||||
// neither giver nor recipient are named
|
// neither giver nor recipient are named
|
||||||
|
|
||||||
|
// show the project name if to one
|
||||||
|
if (giveRecord.recipientProjectName) {
|
||||||
|
// retrieve the project name
|
||||||
|
return `${gaveAmount} (to the project ${giveRecord.recipientProjectName})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's not to a project
|
||||||
let peopleInfo;
|
let peopleInfo;
|
||||||
if (giverInfo.displayName === recipientInfo.displayName) {
|
if (giverInfo.displayName === recipientInfo.displayName) {
|
||||||
peopleInfo = `between two who are ${giverInfo.displayName}`;
|
peopleInfo = `between two who are ${giverInfo.displayName}`;
|
||||||
@@ -576,5 +702,9 @@ export default class HomeView extends Vue {
|
|||||||
openGiftedPrompts() {
|
openGiftedPrompts() {
|
||||||
(this.$refs.giftedPrompts as GiftedPrompts).open();
|
(this.$refs.giftedPrompts as GiftedPrompts).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openFeedFilters() {
|
||||||
|
(this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -362,11 +362,11 @@ import { accessToken } from "@/libs/crypto";
|
|||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
import {
|
import {
|
||||||
BLANK_GENERIC_SERVER_RECORD,
|
BLANK_GENERIC_SERVER_RECORD,
|
||||||
GenericServerRecord,
|
GenericCredWrapper,
|
||||||
GiverInputInfo,
|
GiverInputInfo,
|
||||||
GiveServerRecord,
|
GiveSummaryRecord,
|
||||||
OfferServerRecord,
|
OfferSummaryRecord,
|
||||||
PlanServerRecord,
|
PlanSummaryRecord,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import * as serverUtil from "@/libs/endorserServer";
|
import * as serverUtil from "@/libs/endorserServer";
|
||||||
|
|
||||||
@@ -390,14 +390,14 @@ export default class ProjectViewView extends Vue {
|
|||||||
apiServer = "";
|
apiServer = "";
|
||||||
description = "";
|
description = "";
|
||||||
expanded = false;
|
expanded = false;
|
||||||
fulfilledByThis: PlanServerRecord | null = null;
|
fulfilledByThis: PlanSummaryRecord | null = null;
|
||||||
fulfillersToThis: Array<PlanServerRecord> = [];
|
fulfillersToThis: Array<PlanSummaryRecord> = [];
|
||||||
givesToThis: Array<GiveServerRecord> = [];
|
givesToThis: Array<GiveSummaryRecord> = [];
|
||||||
issuer = "";
|
issuer = "";
|
||||||
latitude = 0;
|
latitude = 0;
|
||||||
longitude = 0;
|
longitude = 0;
|
||||||
name = "";
|
name = "";
|
||||||
offersToThis: Array<OfferServerRecord> = [];
|
offersToThis: Array<OfferSummaryRecord> = [];
|
||||||
projectId = localStorage.getItem("projectId") || ""; // handle ID
|
projectId = localStorage.getItem("projectId") || ""; // handle ID
|
||||||
showDidCopy = false;
|
showDidCopy = false;
|
||||||
timeSince = "";
|
timeSince = "";
|
||||||
@@ -720,8 +720,8 @@ export default class ProjectViewView extends Vue {
|
|||||||
this.$router.push(route);
|
this.$router.push(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIsFulfillable(offer: OfferServerRecord) {
|
checkIsFulfillable(offer: OfferSummaryRecord) {
|
||||||
const offerRecord: GenericServerRecord = {
|
const offerRecord: GenericCredWrapper = {
|
||||||
...BLANK_GENERIC_SERVER_RECORD,
|
...BLANK_GENERIC_SERVER_RECORD,
|
||||||
claim: offer.fullClaim,
|
claim: offer.fullClaim,
|
||||||
claimType: "Offer",
|
claimType: "Offer",
|
||||||
@@ -730,8 +730,8 @@ export default class ProjectViewView extends Vue {
|
|||||||
return libsUtil.canFulfillOffer(offerRecord);
|
return libsUtil.canFulfillOffer(offerRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickFulfillGiveToOffer(offer: OfferServerRecord) {
|
onClickFulfillGiveToOffer(offer: OfferSummaryRecord) {
|
||||||
const offerRecord: GenericServerRecord = {
|
const offerRecord: GenericCredWrapper = {
|
||||||
...BLANK_GENERIC_SERVER_RECORD,
|
...BLANK_GENERIC_SERVER_RECORD,
|
||||||
claim: offer.fullClaim,
|
claim: offer.fullClaim,
|
||||||
issuer: offer.offeredByDid,
|
issuer: offer.offeredByDid,
|
||||||
@@ -770,8 +770,8 @@ export default class ProjectViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIsConfirmable(give: GiveServerRecord) {
|
checkIsConfirmable(give: GiveSummaryRecord) {
|
||||||
const giveDetails: GenericServerRecord = {
|
const giveDetails: GenericCredWrapper = {
|
||||||
...BLANK_GENERIC_SERVER_RECORD,
|
...BLANK_GENERIC_SERVER_RECORD,
|
||||||
claim: give.fullClaim,
|
claim: give.fullClaim,
|
||||||
claimType: "GiveAction",
|
claimType: "GiveAction",
|
||||||
@@ -781,7 +781,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// similar code is found in ClaimView
|
// similar code is found in ClaimView
|
||||||
async confirmClaim(give: GiveServerRecord) {
|
async confirmClaim(give: GiveSummaryRecord) {
|
||||||
if (confirm("Do you personally confirm that this is true?")) {
|
if (confirm("Do you personally confirm that this is true?")) {
|
||||||
// similar logic is found in endorser-mobile
|
// similar logic is found in endorser-mobile
|
||||||
const goodClaim = serverUtil.removeSchemaContext(
|
const goodClaim = serverUtil.removeSchemaContext(
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
|||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.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 { OfferServerRecord, PlanData } from "@/libs/endorserServer";
|
import { OfferSummaryRecord, PlanData } from "@/libs/endorserServer";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -237,7 +237,7 @@ export default class ProjectsView extends Vue {
|
|||||||
currentIid: IIdentifier;
|
currentIid: IIdentifier;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
offers: OfferServerRecord[] = [];
|
offers: OfferSummaryRecord[] = [];
|
||||||
showOffers = true;
|
showOffers = true;
|
||||||
showProjects = false;
|
showProjects = false;
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ import {
|
|||||||
createAndSubmitConfirmation,
|
createAndSubmitConfirmation,
|
||||||
createAndSubmitGive,
|
createAndSubmitGive,
|
||||||
ErrorResult,
|
ErrorResult,
|
||||||
GenericServerRecord,
|
GenericCredWrapper,
|
||||||
GenericVerifiableCredential,
|
GenericVerifiableCredential,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
@@ -166,7 +166,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
claimCountWithHidden = 0;
|
claimCountWithHidden = 0;
|
||||||
claimsToConfirm: GenericServerRecord[] = [];
|
claimsToConfirm: GenericCredWrapper[] = [];
|
||||||
claimsToConfirmSelected: string[] = [];
|
claimsToConfirmSelected: string[] = [];
|
||||||
description = "breakfast";
|
description = "breakfast";
|
||||||
loadingConfirms = true;
|
loadingConfirms = true;
|
||||||
@@ -228,7 +228,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
}
|
}
|
||||||
await response.json().then((data) => {
|
await response.json().then((data) => {
|
||||||
const dataByOthers = R.reject(
|
const dataByOthers = R.reject(
|
||||||
(claim: GenericServerRecord) => claim.issuer === this.activeDid,
|
(claim: GenericCredWrapper) => claim.issuer === this.activeDid,
|
||||||
data,
|
data,
|
||||||
);
|
);
|
||||||
const dataByOthersWithoutHidden = R.reject(
|
const dataByOthersWithoutHidden = R.reject(
|
||||||
|
|||||||
@@ -209,9 +209,9 @@ export default class DiscoverView extends Vue {
|
|||||||
group: "alert",
|
group: "alert",
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Saved",
|
title: "Saved",
|
||||||
text: "That has been saved in your preferences.",
|
text: "That has been saved in your preferences. You can now filter by it on your home screen feed.",
|
||||||
},
|
},
|
||||||
-1,
|
7000,
|
||||||
);
|
);
|
||||||
this.$router.back();
|
this.$router.back();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -247,6 +247,7 @@ export default class DiscoverView extends Vue {
|
|||||||
await db.open();
|
await db.open();
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
searchBoxes: [],
|
searchBoxes: [],
|
||||||
|
filterFeedByNearby: false,
|
||||||
});
|
});
|
||||||
this.searchBox = null;
|
this.searchBox = null;
|
||||||
this.localCenterLat = 0;
|
this.localCenterLat = 0;
|
||||||
|
|||||||
@@ -1,47 +1,35 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true,
|
"target": "ES2020", // Latest ECMAScript features that are widely supported by modern browsers
|
||||||
"resolveJsonModule": true,
|
"module": "ESNext", // Use ES modules
|
||||||
"target": "esnext",
|
"strict": true, // Enable all strict type checking options
|
||||||
"module": "esnext",
|
"jsx": "preserve", // Preserves JSX to be transformed by Babel or another transpiler
|
||||||
"strict": true,
|
"moduleResolution": "node", // Use Node.js style module resolution
|
||||||
"strictPropertyInitialization": false,
|
"experimentalDecorators": true,
|
||||||
"jsx": "preserve",
|
"esModuleInterop": true, // Enables compatibility with CommonJS modules for default imports
|
||||||
"moduleResolution": "node",
|
"allowSyntheticDefaultImports": true, // Allow default imports from modules with no default export
|
||||||
"experimentalDecorators": true,
|
"forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file
|
||||||
"skipLibCheck": true,
|
"useDefineForClassFields": true,
|
||||||
"esModuleInterop": true,
|
"sourceMap": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"baseUrl": "./src", // Base directory to resolve non-relative module names
|
||||||
"forceConsistentCasingInFileNames": true,
|
"paths": {
|
||||||
"useDefineForClassFields": true,
|
"@/components/*": ["components/*"],
|
||||||
"sourceMap": true,
|
"@/views/*": ["views/*"],
|
||||||
"baseUrl": "./src",
|
"@/db/*": ["db/*"],
|
||||||
"types": [
|
"@/libs/*": ["libs/*"],
|
||||||
"webpack-env"
|
"@/constants/*": ["constants/*"],
|
||||||
],
|
"@/store/*": ["store/*"]
|
||||||
"paths": {
|
},
|
||||||
"@/components/*": ["components/*"],
|
"lib": ["ES2020", "dom", "dom.iterable"], // Include typings for ES2020 and DOM APIs
|
||||||
"@/views/*": ["views/*"],
|
|
||||||
"@/db/*": ["db/*"],
|
|
||||||
"@/libs/*": ["libs/*"],
|
|
||||||
"@/constants/*": ["constants/*"],
|
|
||||||
"@/store/*": ["store/*"],
|
|
||||||
},
|
},
|
||||||
"lib": [
|
"include": [
|
||||||
"esnext",
|
"src/**/*.ts",
|
||||||
"dom",
|
"src/**/*.tsx",
|
||||||
"dom.iterable",
|
"src/**/*.vue",
|
||||||
"scripthost"
|
"tests/**/*.ts",
|
||||||
|
"tests/**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*.ts",
|
|
||||||
"src/**/*.tsx",
|
|
||||||
"src/**/*.vue",
|
|
||||||
"tests/**/*.ts",
|
|
||||||
"tests/**/*.tsx"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
vite.config.mjs
Normal file
18
vite.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
port: 8080
|
||||||
|
},
|
||||||
|
plugins: [ vue() ],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
buffer: path.resolve(__dirname, 'node_modules', 'buffer'),
|
||||||
|
'dexie-export-import/dist/import': 'dexie-export-import/dist/import/index.js',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -2,9 +2,9 @@ const { defineConfig } = require("@vue/cli-service");
|
|||||||
const { gitDescribeSync } = require("git-describe");
|
const { gitDescribeSync } = require("git-describe");
|
||||||
const { exec } = require("child_process");
|
const { exec } = require("child_process");
|
||||||
|
|
||||||
process.env.VUE_APP_GIT_HASH = gitDescribeSync().hash;
|
import.meta.env.VITE_GIT_HASH = gitDescribeSync().hash;
|
||||||
const TIME_SAFARI_APP_TITLE =
|
const TIME_SAFARI_APP_TITLE =
|
||||||
process.env.TIME_SAFARI_APP_TITLE || require("./package.json").name;
|
import.meta.env.VITE_TIME_SAFARI_APP_TITLE || require("./package.json").name;
|
||||||
|
|
||||||
module.exports = defineConfig({
|
module.exports = defineConfig({
|
||||||
transpileDependencies: true,
|
transpileDependencies: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user