forked from trent_larson/crowd-funder-for-time-pwa
Compare commits
1 Commits
claim-view
...
sw-cleanup
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f0d10d93d |
24
CHANGELOG.md
24
CHANGELOG.md
@@ -8,30 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
|
||||||
## [0.1.6] - 2023.12.17
|
|
||||||
### Added
|
### Added
|
||||||
- Infinite scroll on home page
|
- Web push notifications
|
||||||
### Changed
|
|
||||||
- UI improvements
|
|
||||||
- Show web-push subscription info
|
|
||||||
- Icon
|
|
||||||
|
|
||||||
|
|
||||||
## [0.1.5] - 2023.12.09 - 9c36bb509a9bae9bb3306d3bd9eeb144b67aa8ad
|
|
||||||
### Added
|
|
||||||
- Web push notifications (though not finalized)
|
|
||||||
- Credentials details page
|
|
||||||
- See more data without an ID
|
|
||||||
- Change units of a give
|
|
||||||
|
|
||||||
|
|
||||||
## [0.1.4] - 2023.11.20 - 7311d36726f3667ec4c68f241f91d404273ad4db
|
|
||||||
### Added
|
|
||||||
- Offer on a project
|
|
||||||
### Changed
|
|
||||||
- Automatically set as visible when importing a contact
|
|
||||||
|
|
||||||
|
|
||||||
## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde
|
## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
34
README.md
34
README.md
@@ -22,25 +22,16 @@ npm run lint
|
|||||||
|
|
||||||
If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
|
If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
|
||||||
|
|
||||||
* `npx prettier --write ./sw_scripts/`
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
...to make sure the service worker scripts are in proper form
|
```
|
||||||
|
npx prettier --write ./sw_scripts/
|
||||||
|
```
|
||||||
|
to make sure the service worker scripts are in proper form
|
||||||
|
|
||||||
* Update the CHANGELOG.md & the version in package.json, run `npm install`, and commit.
|
... then copy the contents of the `sw_scripts` folder to the `dist` folder - except additional_scripts.js.
|
||||||
|
|
||||||
* Tag wth the new version: `git tag 0.1.0`. Increment version, add "-beta", `npm install`, and commit.
|
|
||||||
|
|
||||||
* If production, change src/constants/app.ts DEFAULT_*_SERVER to be PROD.
|
|
||||||
|
|
||||||
* `npm run build`
|
|
||||||
|
|
||||||
* Revert src/constants/app.ts
|
|
||||||
|
|
||||||
* `cp sw_scripts/[ns]* dist/`
|
|
||||||
|
|
||||||
... to copy the contents of the `sw_scripts` folder to the `dist` folder - except additional_scripts.js.
|
|
||||||
|
|
||||||
* `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntu@endorser.ch:time-safari`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -71,7 +62,7 @@ Under the "Your Identity" screen, click "Advanced", click "Switch Identity / No
|
|||||||
|
|
||||||
### Web-push
|
### Web-push
|
||||||
|
|
||||||
For your own web-push tests, change the push server URL in Advanced settings on the account page, and install Time Safari & push server on the same domain.
|
For your own web-push tests, change the 'vapid' URL in App.vue, and install apps on the same domain.
|
||||||
|
|
||||||
### Icons
|
### Icons
|
||||||
|
|
||||||
@@ -112,10 +103,9 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib
|
|||||||
|
|
||||||
### Clear/Reset data & restart
|
### Clear/Reset data & restart
|
||||||
|
|
||||||
* Clear cache for site. (In Chrome, go to `chrome://settings/cookies` and "all site data and permissions"; in Firefox, go to `about:preferences` and search for "cache" then "Manage Data".)
|
* Clear cache for localhost.
|
||||||
* Unregister service worker (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers`).
|
* Unregister service worker (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers` or `about:debugging`).
|
||||||
* Clear notification permission (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search for "notifications").
|
* Clear notification permission (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search).
|
||||||
* Clear Cache Storage (in Chrome, in dev tools under Application; in Firefox, in dev tools under Storage).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
163
package-lock.json
generated
163
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "TimeSafari",
|
"name": "crowd-funder-for-time-pwa",
|
||||||
"version": "0.1.7-beta",
|
"version": "0.1.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "TimeSafari",
|
"name": "crowd-funder-for-time-pwa",
|
||||||
"version": "0.1.7-beta",
|
"version": "0.1.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethersproject/hdnode": "^5.7.0",
|
"@ethersproject/hdnode": "^5.7.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
"ethereum-cryptography": "^2.1.2",
|
"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",
|
|
||||||
"jdenticon": "^3.2.0",
|
"jdenticon": "^3.2.0",
|
||||||
"js-generate-password": "^0.1.9",
|
"js-generate-password": "^0.1.9",
|
||||||
"localstorage-slim": "^2.5.0",
|
"localstorage-slim": "^2.5.0",
|
||||||
@@ -50,7 +49,6 @@
|
|||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"three": "^0.156.1",
|
"three": "^0.156.1",
|
||||||
"util": "^0.12.5",
|
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^3.0.2",
|
"vue-facing-decorator": "^3.0.2",
|
||||||
@@ -74,13 +72,13 @@
|
|||||||
"@vue/cli-service": "~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.15",
|
||||||
"eslint": "^8.53.0",
|
"eslint": "^8.48.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"postcss": "^8.4.29",
|
"postcss": "^8.4.29",
|
||||||
"prettier": "^3.1.0",
|
"prettier": "^3.0.3",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "~5.2.2"
|
"typescript": "~5.2.2"
|
||||||
}
|
}
|
||||||
@@ -2823,9 +2821,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
|
||||||
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
|
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
@@ -2873,9 +2871,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "8.55.0",
|
"version": "8.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz",
|
||||||
"integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
|
"integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
@@ -5500,12 +5498,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.13",
|
"version": "0.11.11",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
|
||||||
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
|
"integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@humanwhocodes/object-schema": "^2.0.1",
|
"@humanwhocodes/object-schema": "^1.2.1",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"minimatch": "^3.0.5"
|
"minimatch": "^3.0.5"
|
||||||
},
|
},
|
||||||
@@ -5527,9 +5525,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/object-schema": {
|
"node_modules/@humanwhocodes/object-schema": {
|
||||||
"version": "2.0.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
|
||||||
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@jest/create-cache-key-function": {
|
"node_modules/@jest/create-cache-key-function": {
|
||||||
@@ -8821,11 +8819,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.10.4",
|
"version": "20.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz",
|
||||||
"integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==",
|
"integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.25.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/normalize-package-data": {
|
"node_modules/@types/normalize-package-data": {
|
||||||
@@ -8895,7 +8893,8 @@
|
|||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "7.5.3",
|
"version": "7.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz",
|
||||||
"integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw=="
|
"integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/send": {
|
"node_modules/@types/send": {
|
||||||
"version": "0.17.2",
|
"version": "0.17.2",
|
||||||
@@ -9207,12 +9206,6 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ungap/structured-clone": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/@unimodules/core": {
|
"node_modules/@unimodules/core": {
|
||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@unimodules/core/-/core-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@unimodules/core/-/core-7.1.2.tgz",
|
||||||
@@ -11252,6 +11245,7 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
|
||||||
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
|
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -12069,6 +12063,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||||
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
"get-intrinsic": "^1.0.2"
|
"get-intrinsic": "^1.0.2"
|
||||||
@@ -14225,19 +14220,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "8.55.0",
|
"version": "8.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz",
|
||||||
"integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
|
"integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
"@eslint/eslintrc": "^2.1.4",
|
"@eslint/eslintrc": "^2.1.2",
|
||||||
"@eslint/js": "8.55.0",
|
"@eslint/js": "8.51.0",
|
||||||
"@humanwhocodes/config-array": "^0.11.13",
|
"@humanwhocodes/config-array": "^0.11.11",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
"@ungap/structured-clone": "^1.2.0",
|
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"cross-spawn": "^7.0.2",
|
||||||
@@ -15897,6 +15891,7 @@
|
|||||||
"version": "0.3.3",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||||
"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
|
"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-callable": "^1.1.3"
|
"is-callable": "^1.1.3"
|
||||||
}
|
}
|
||||||
@@ -16156,6 +16151,7 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"devOptional": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@@ -16208,6 +16204,7 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||||
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
||||||
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
@@ -16272,30 +16269,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/git-describe": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/git-describe/-/git-describe-4.1.1.tgz",
|
|
||||||
"integrity": "sha512-JC8ganO5kO80G8+XE98TDDjnMXQN3Estk3qdJuG2EGRF/l6zuMTMcN+8OSfQZ5FWpqIRLB015anWX4aSRgnxAQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/semver": "^7.3.8",
|
|
||||||
"lodash": "^4.17.21"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"semver": "^5.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/git-describe/node_modules/semver": {
|
|
||||||
"version": "5.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
|
||||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
|
||||||
"optional": true,
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
@@ -16382,6 +16355,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"get-intrinsic": "^1.1.3"
|
"get-intrinsic": "^1.1.3"
|
||||||
},
|
},
|
||||||
@@ -16452,6 +16426,7 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
|
||||||
"integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
|
"integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
|
||||||
|
"devOptional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
@@ -16490,6 +16465,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||||
|
"devOptional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -16501,6 +16477,7 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||||
|
"devOptional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -16512,6 +16489,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
||||||
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.2"
|
"has-symbols": "^1.0.2"
|
||||||
},
|
},
|
||||||
@@ -17103,21 +17081,6 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-arguments": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
|
|
||||||
"dependencies": {
|
|
||||||
"call-bind": "^1.0.2",
|
|
||||||
"has-tostringtag": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-array-buffer": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
|
||||||
@@ -17189,6 +17152,7 @@
|
|||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||||
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
|
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -17293,20 +17257,6 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-generator-function": {
|
|
||||||
"version": "1.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
|
|
||||||
"integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
|
|
||||||
"dependencies": {
|
|
||||||
"has-tostringtag": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-glob": {
|
"node_modules/is-glob": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
@@ -17580,6 +17530,7 @@
|
|||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
|
||||||
"integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
|
"integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"which-typed-array": "^1.1.11"
|
"which-typed-array": "^1.1.11"
|
||||||
},
|
},
|
||||||
@@ -19362,7 +19313,8 @@
|
|||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/lodash.clonedeep": {
|
"node_modules/lodash.clonedeep": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
@@ -23185,9 +23137,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.1.0",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
|
||||||
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
|
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
@@ -26918,9 +26870,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "5.26.5",
|
"version": "5.25.3",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
"integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA=="
|
||||||
},
|
},
|
||||||
"node_modules/unicode-canonical-property-names-ecmascript": {
|
"node_modules/unicode-canonical-property-names-ecmascript": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -27103,18 +27055,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
|
||||||
"integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ=="
|
"integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ=="
|
||||||
},
|
},
|
||||||
"node_modules/util": {
|
|
||||||
"version": "0.12.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
|
||||||
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
|
|
||||||
"dependencies": {
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"is-arguments": "^1.0.4",
|
|
||||||
"is-generator-function": "^1.0.7",
|
|
||||||
"is-typed-array": "^1.1.3",
|
|
||||||
"which-typed-array": "^1.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
@@ -28026,6 +27966,7 @@
|
|||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz",
|
||||||
"integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==",
|
"integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"available-typed-arrays": "^1.0.5",
|
"available-typed-arrays": "^1.0.5",
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "TimeSafari",
|
"name": "crowd-funder-for-time-pwa",
|
||||||
"version": "0.1.7-beta",
|
"version": "0.1.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
"ethereum-cryptography": "^2.1.2",
|
"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",
|
|
||||||
"jdenticon": "^3.2.0",
|
"jdenticon": "^3.2.0",
|
||||||
"js-generate-password": "^0.1.9",
|
"js-generate-password": "^0.1.9",
|
||||||
"localstorage-slim": "^2.5.0",
|
"localstorage-slim": "^2.5.0",
|
||||||
@@ -50,7 +49,6 @@
|
|||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"three": "^0.156.1",
|
"three": "^0.156.1",
|
||||||
"util": "^0.12.5",
|
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^3.0.2",
|
"vue-facing-decorator": "^3.0.2",
|
||||||
|
|||||||
@@ -1,39 +1,29 @@
|
|||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
- 08 notifications :
|
- 40 notifications :
|
||||||
- get it to work on Android - background data in apps
|
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew
|
||||||
- get it to work on iOS
|
- extract private_key_hex in py-push-server webpush.py
|
||||||
- make the app behave correctly when App Notifications are turned off
|
- lock down regenerate_vapid endpoint (so only we admins can do it on demand)
|
||||||
|
- remove sleep in py-push-server app.py
|
||||||
|
- revisit "maybe" and "never" buttons on accont screen
|
||||||
|
- see if we can detect OS-level notifications if turned off
|
||||||
- write troubleshooting docs for notifications
|
- write troubleshooting docs for notifications
|
||||||
- hide the "App Notifications" toggle when they switch notifications
|
|
||||||
- prompt user to install on their home screen https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getInstalledRelatedApps
|
|
||||||
- warn if they're using the web (android only?)
|
|
||||||
https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getInstalledRelatedApps
|
|
||||||
https://web.dev/articles/get-installed-related-apps
|
|
||||||
- add windows & mac help at OS & browser level
|
|
||||||
|
|
||||||
- back-and-forth on discovery & project pages led to "You need an identity to load your projects." error on product page when I had an identity
|
- .3 fix the Project-location-selection map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui
|
||||||
- fix the projects on /discover to show the issuer (currently all "Someone Anonymous")
|
|
||||||
|
|
||||||
- .3 bug - make or edit a project, choose "Include location", and see the map display shows on top of the bottom icons assignee-group:ui
|
- .5 Add infinite scroll to gifts on the home page
|
||||||
- Got error adding on Firefox user #0 as contact for themselves
|
|
||||||
|
|
||||||
- .5 If notifications are not enabled, add message to front page with link/button to enable
|
- .5 If notifications are not enabled, add message to front page with link/button to enable
|
||||||
|
|
||||||
- 01 server - show all claim details when issued by the issuer
|
|
||||||
- enhance help page instructions for debugging
|
|
||||||
- add way to test quickly a push notification
|
|
||||||
- help instructions for PWA install problems (secret failed, must reinstall)
|
|
||||||
- look at other examples for better UI friend.tech
|
|
||||||
|
|
||||||
- show VC details... somehow:
|
- show VC details... somehow:
|
||||||
- 01 show my VCs - most interesting, or via search
|
- .5 make a VC details page, or link to endorser.ch (including confirmations)
|
||||||
- 01 allow download of each VC (& confirmations, to show that they actually own their data)
|
- 01 allow download of each VC (& confirmations, to show that they actually own their data)
|
||||||
- 04 allow user to download VCs, mine + ones I can see about me from others
|
- 04 allow user to download VCs, mine + ones I can see about me from others
|
||||||
- add VC confirmation?
|
- add VC confirmation?
|
||||||
|
|
||||||
- Release Minimum Viable Product :
|
- Release Minimum Viable Product :
|
||||||
|
- generate new webpush.db entry, webpush.py private_key_hex & subscription_info & vapid_claims email
|
||||||
- .5 deploy endorser.ch server above Dec 1 (to get plan searches by names as well as descriptions)
|
- .5 deploy endorser.ch server above Dec 1 (to get plan searches by names as well as descriptions)
|
||||||
- 08 thorough testing for errors & edge cases
|
- 08 thorough testing for errors & edge cases
|
||||||
- 01 ensure ability to recover server remotely, and add redundant access
|
- 01 ensure ability to recover server remotely, and add redundant access
|
||||||
@@ -43,13 +33,10 @@ tasks:
|
|||||||
- Deploy to a server.
|
- Deploy to a server.
|
||||||
- Ensure public server has limits that work for group adoption.
|
- Ensure public server has limits that work for group adoption.
|
||||||
- Test PWA features on Android and iOS.
|
- Test PWA features on Android and iOS.
|
||||||
- Other features - donation vs give, show offers, show give & outstanding totals, show network view, restrict registration, connect to contacts
|
|
||||||
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
|
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
|
||||||
|
|
||||||
- 01 send visibility signal as a VC and store it
|
|
||||||
- remove 'rowid' references (that are sqlite-specific)
|
|
||||||
- make identicons for contacts into more-memorable faces (and maybe change project identicons, too)
|
- make identicons for contacts into more-memorable faces (and maybe change project identicons, too)
|
||||||
- 01 make the prod build copy the sw_scripts
|
- allow some gives even if they aren't registered
|
||||||
- .5 Add start date to project
|
- .5 Add start date to project
|
||||||
- .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?
|
||||||
@@ -63,9 +50,6 @@ tasks:
|
|||||||
- switch some checks for activeDid to check for isRegistered
|
- switch some checks for activeDid to check for isRegistered
|
||||||
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
|
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
|
||||||
- .5 fix cert generation on server (since it didn't happen automatically for Nov 30)
|
- .5 fix cert generation on server (since it didn't happen automatically for Nov 30)
|
||||||
|
|
||||||
- 04 fix lack of initial notification in Firefox (on MacOS, maybe others)
|
|
||||||
|
|
||||||
- contacts v+ :
|
- contacts v+ :
|
||||||
- 01 Import all the non-sensitive data (ie. contacts & settings).
|
- 01 Import all the non-sensitive data (ie. contacts & settings).
|
||||||
- .2 show error to user when adding a duplicate contact
|
- .2 show error to user when adding a duplicate contact
|
||||||
@@ -107,8 +91,6 @@ tasks:
|
|||||||
- automated tests, eg. cypress
|
- automated tests, eg. cypress
|
||||||
|
|
||||||
- Notifications (wake on the phone, push notifications)
|
- Notifications (wake on the phone, push notifications)
|
||||||
- pull instead of push, maybe via scheduled runs
|
|
||||||
- have a notification pop-up on Mac screen
|
|
||||||
|
|
||||||
- Connect with phone contacts
|
- Connect with phone contacts
|
||||||
|
|
||||||
@@ -121,11 +103,12 @@ tasks:
|
|||||||
|
|
||||||
- Do we want split first name & last name?
|
- Do we want split first name & last name?
|
||||||
|
|
||||||
|
- 40 notifications v+ :
|
||||||
|
- pull, w/ scheduled runs
|
||||||
|
|
||||||
- 01 On nearby search, if user starts changing their box but cancels and goes back to the map it is zoomed far out. Fix to fit the box better.
|
- 01 On nearby search, if user starts changing their box but cancels and goes back to the map it is zoomed far out. Fix to fit the box better.
|
||||||
- 16 From the home screen, make the quick action even easier.
|
- 16 From the home screen, make the quick action even easier.
|
||||||
|
|
||||||
- allow some gives even if they aren't registered - maybe someday as a gift to the world, but we really want this to be built via personal connections
|
|
||||||
|
|
||||||
log:
|
log:
|
||||||
- videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29
|
- videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29
|
||||||
- project lists, contact totals & actions, multiple identifiers, stats-world, activity feed, rename of this project file (use "--follow --") milestone:2 done:2023-06-27
|
- project lists, contact totals & actions, multiple identifiers, stats-world, activity feed, rename of this project file (use "--follow --") milestone:2 done:2023-06-27
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 4.2 KiB |
119
src/App.vue
119
src/App.vue
@@ -169,12 +169,20 @@
|
|||||||
>
|
>
|
||||||
Turn on Notifications
|
Turn on Notifications
|
||||||
</button>
|
</button>
|
||||||
<button
|
<div class="grid grid-cols-2 gap-2">
|
||||||
@click="close(notification.id)"
|
<button
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
@click="maybeLater(notification.id)"
|
||||||
>
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
Maybe Later
|
>
|
||||||
</button>
|
Maybe Later
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="never(notification.id)"
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
Never
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -230,10 +238,6 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="
|
|
||||||
close(notification.id);
|
|
||||||
turnOffNotifications();
|
|
||||||
"
|
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
>
|
>
|
||||||
Turn Off Notifications
|
Turn Off Notifications
|
||||||
@@ -328,23 +332,16 @@ export default class App extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (window.location.host.startsWith("localhost")) {
|
console.error("Got an error initializing notifications:", error);
|
||||||
console.log(
|
this.$notify(
|
||||||
"Ignoring this error getting VAPID for local development:",
|
{
|
||||||
error,
|
group: "alert",
|
||||||
);
|
type: "danger",
|
||||||
} else {
|
title: "Error Setting Notifications",
|
||||||
console.error("Got an error initializing notifications:", error);
|
text: "Got an error setting notifications.",
|
||||||
this.$notify(
|
},
|
||||||
{
|
-1,
|
||||||
group: "alert",
|
);
|
||||||
type: "danger",
|
|
||||||
title: "Error Setting Notifications",
|
|
||||||
text: "Got an error setting notifications.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,10 +410,7 @@ export default class App extends Vue {
|
|||||||
private requestNotificationPermission(): Promise<NotificationPermission> {
|
private requestNotificationPermission(): Promise<NotificationPermission> {
|
||||||
return Notification.requestPermission().then((permission) => {
|
return Notification.requestPermission().then((permission) => {
|
||||||
if (permission !== "granted") {
|
if (permission !== "granted") {
|
||||||
alert(
|
alert("We need notification permission to provide certain features.");
|
||||||
"Allow this app permission to make notifications for personal reminders." +
|
|
||||||
" You can adjust them at any time in your settings.",
|
|
||||||
);
|
|
||||||
throw new Error("We weren't granted permission.");
|
throw new Error("We weren't granted permission.");
|
||||||
}
|
}
|
||||||
return permission;
|
return permission;
|
||||||
@@ -452,22 +446,16 @@ export default class App extends Vue {
|
|||||||
"Subscription or server communication failed:",
|
"Subscription or server communication failed:",
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
alert(
|
alert( "Subscription or server communication failed. Try again in a while." );
|
||||||
"Subscription or server communication failed. Try again in a while.",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(
|
console.error("An error occurred:", error);
|
||||||
"An error occurred setting notification permissions:",
|
alert( "Some error occurred." + error );
|
||||||
error,
|
|
||||||
);
|
|
||||||
alert(
|
|
||||||
"Some error occurred setting notification permissions. See logs.",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private urlBase64ToUint8Array(base64String: string): Uint8Array {
|
private urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||||
const base64 = (base64String + padding)
|
const base64 = (base64String + padding)
|
||||||
@@ -531,7 +519,7 @@ export default class App extends Vue {
|
|||||||
private sendSubscriptionToServer(
|
private sendSubscriptionToServer(
|
||||||
subscription: PushSubscription,
|
subscription: PushSubscription,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log("About to send subscription", subscription);
|
console.log(subscription);
|
||||||
return fetch("/web-push/subscribe", {
|
return fetch("/web-push/subscribe", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -546,49 +534,12 @@ export default class App extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async turnOffNotifications() {
|
never(ID: string) {
|
||||||
let subscription;
|
alert(ID);
|
||||||
const pushProviderSuccess = await navigator.serviceWorker.ready
|
}
|
||||||
.then((registration) => {
|
|
||||||
return registration.pushManager.getSubscription();
|
|
||||||
})
|
|
||||||
.then((subscript) => {
|
|
||||||
subscription = subscript;
|
|
||||||
if (subscription) {
|
|
||||||
return subscription.unsubscribe();
|
|
||||||
} else {
|
|
||||||
console.log("Subscription object is not available.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log("Push provider server communication failed:", error);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const pushServerSuccess = await fetch("/web-push/unsubscribe", {
|
maybeLater(ID: string) {
|
||||||
method: "POST",
|
alert(ID);
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(subscription),
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
return response.ok;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log("Push server communication failed:", error);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
alert(
|
|
||||||
"Notifications are off. Push provider unsubscribe " +
|
|
||||||
(pushProviderSuccess ? "succeeded" : "failed") +
|
|
||||||
(pushProviderSuccess === pushServerSuccess ? " and" : " but") +
|
|
||||||
" push server unsubscribe " +
|
|
||||||
(pushServerSuccess ? "succeeded" : "failed") +
|
|
||||||
".",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 6.7 KiB |
@@ -13,21 +13,18 @@
|
|||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<span
|
<span
|
||||||
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 w-1/3 text-center px-2 py-2"
|
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 w-1/3 text-center px-2 py-2"
|
||||||
@click="changeUnitCode()"
|
>Hours</span
|
||||||
>
|
>
|
||||||
{{ UNIT_SHORT[unitCode] }}
|
|
||||||
</span>
|
|
||||||
<div
|
<div
|
||||||
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
@click="decrement()"
|
@click="decrement()"
|
||||||
v-if="amountInput !== '0'"
|
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" />
|
<fa icon="chevron-left" />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="w-full border border-r-0 border-slate-400 px-2 py-2 text-center"
|
class="w-full border border-r-0 border-slate-400 px-2 py-2 text-center"
|
||||||
v-model="amountInput"
|
v-model="hours"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
@@ -84,31 +81,12 @@ export default class GiftedDialog extends Vue {
|
|||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
|
|
||||||
amountInput = "0";
|
|
||||||
giver?: GiverInputInfo; // undefined means no identified giver agent
|
giver?: GiverInputInfo; // undefined means no identified giver agent
|
||||||
description = "";
|
description = "";
|
||||||
givenToUser = false;
|
givenToUser = false;
|
||||||
unitCode = "HUR";
|
hours = "0";
|
||||||
visible = false;
|
visible = false;
|
||||||
|
|
||||||
/* eslint-disable prettier/prettier */
|
|
||||||
UNIT_SHORT: Record<string, string> = {
|
|
||||||
"BTC": "BTC",
|
|
||||||
"ETH": "ETH",
|
|
||||||
"HUR": "Hours",
|
|
||||||
"USD": "US $",
|
|
||||||
};
|
|
||||||
/* eslint-enable prettier/prettier */
|
|
||||||
|
|
||||||
/* eslint-disable prettier/prettier */
|
|
||||||
UNIT_LONG: Record<string, string> = {
|
|
||||||
"BTC": "BTC",
|
|
||||||
"ETH": "ETH",
|
|
||||||
"HUR": "hours",
|
|
||||||
"USD": "dollars",
|
|
||||||
};
|
|
||||||
/* eslint-enable prettier/prettier */
|
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
await db.open();
|
await db.open();
|
||||||
@@ -137,7 +115,7 @@ export default class GiftedDialog extends Vue {
|
|||||||
this.giver = giver;
|
this.giver = giver;
|
||||||
// if we show "given to user" selection, default checkbox to true
|
// if we show "given to user" selection, default checkbox to true
|
||||||
this.givenToUser = this.showGivenToUser;
|
this.givenToUser = this.showGivenToUser;
|
||||||
this.amountInput = "0";
|
this.hours = "0";
|
||||||
|
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
}
|
}
|
||||||
@@ -147,21 +125,12 @@ export default class GiftedDialog extends Vue {
|
|||||||
this.visible = false;
|
this.visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
changeUnitCode() {
|
|
||||||
const units = Object.keys(this.UNIT_SHORT);
|
|
||||||
const index = units.indexOf(this.unitCode);
|
|
||||||
this.unitCode = units[(index + 1) % units.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
this.amountInput = `${(parseFloat(this.amountInput) || 0) + 1}`;
|
this.hours = `${(parseFloat(this.hours) || 0) + 1}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
decrement() {
|
decrement() {
|
||||||
this.amountInput = `${Math.max(
|
this.hours = `${Math.max(0, (parseFloat(this.hours) || 1) - 1)}`;
|
||||||
0,
|
|
||||||
(parseFloat(this.amountInput) || 1) - 1,
|
|
||||||
)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
@@ -173,7 +142,7 @@ export default class GiftedDialog extends Vue {
|
|||||||
this.description = "";
|
this.description = "";
|
||||||
this.giver = undefined;
|
this.giver = undefined;
|
||||||
this.givenToUser = this.showGivenToUser;
|
this.givenToUser = this.showGivenToUser;
|
||||||
this.amountInput = "0";
|
this.hours = "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirm() {
|
async confirm() {
|
||||||
@@ -191,8 +160,7 @@ export default class GiftedDialog extends Vue {
|
|||||||
await this.recordGive(
|
await this.recordGive(
|
||||||
this.giver?.did as string | undefined,
|
this.giver?.did as string | undefined,
|
||||||
this.description,
|
this.description,
|
||||||
parseFloat(this.amountInput),
|
parseFloat(this.hours),
|
||||||
this.unitCode,
|
|
||||||
).then(() => {
|
).then(() => {
|
||||||
this.eraseValues();
|
this.eraseValues();
|
||||||
});
|
});
|
||||||
@@ -218,13 +186,12 @@ export default class GiftedDialog extends Vue {
|
|||||||
*
|
*
|
||||||
* @param giverDid may be null
|
* @param giverDid may be null
|
||||||
* @param description may be an empty string
|
* @param description may be an empty string
|
||||||
* @param amountInput may be 0
|
* @param hours may be 0
|
||||||
*/
|
*/
|
||||||
public async recordGive(
|
public async recordGive(
|
||||||
giverDid?: string,
|
giverDid?: string,
|
||||||
description?: string,
|
description?: string,
|
||||||
amountInput?: number,
|
hours?: number,
|
||||||
unitCode?: string,
|
|
||||||
) {
|
) {
|
||||||
if (!this.activeDid) {
|
if (!this.activeDid) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -239,15 +206,13 @@ export default class GiftedDialog extends Vue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!description && !amountInput) {
|
if (!description && !hours) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: `You must enter a description or some number of ${
|
text: "You must enter a description or some number of hours.",
|
||||||
this.UNIT_LONG[this.unitCode]
|
|
||||||
}.`,
|
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -263,8 +228,7 @@ export default class GiftedDialog extends Vue {
|
|||||||
giverDid,
|
giverDid,
|
||||||
this.givenToUser ? this.activeDid : undefined,
|
this.givenToUser ? this.activeDid : undefined,
|
||||||
description,
|
description,
|
||||||
amountInput,
|
hours,
|
||||||
unitCode,
|
|
||||||
this.projectId,
|
this.projectId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="text-center text-red-500">{{ message }}</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
|
||||||
import { db } from "@/db/index";
|
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
|
||||||
import { AppString } from "@/constants/app";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component
|
|
||||||
export default class TopMessage extends Vue {
|
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
@Prop selected = "";
|
|
||||||
|
|
||||||
message = "";
|
|
||||||
|
|
||||||
async mounted() {
|
|
||||||
try {
|
|
||||||
await db.open();
|
|
||||||
|
|
||||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
|
||||||
if (
|
|
||||||
settings?.warnIfTestServer &&
|
|
||||||
window.location.hostname !== AppString.PROD_TIME_SAFARI_SERVER_HOST
|
|
||||||
) {
|
|
||||||
const didPrefix = settings.activeDid?.slice(11, 14);
|
|
||||||
this.message = "You're linked to a test server, user " + didPrefix;
|
|
||||||
} else if (
|
|
||||||
settings?.warnIfProdServer &&
|
|
||||||
window.location.hostname === AppString.PROD_TIME_SAFARI_SERVER_HOST
|
|
||||||
) {
|
|
||||||
const didPrefix = settings.activeDid?.slice(1, 14);
|
|
||||||
this.message =
|
|
||||||
"You're linked to the production server, user " + didPrefix;
|
|
||||||
}
|
|
||||||
} catch (err: unknown) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error Detecting Server",
|
|
||||||
text: JSON.stringify(err),
|
|
||||||
},
|
|
||||||
10000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -6,16 +6,12 @@
|
|||||||
export enum AppString {
|
export enum AppString {
|
||||||
APP_NAME = "Time Safari",
|
APP_NAME = "Time Safari",
|
||||||
|
|
||||||
PROD_TIME_SAFARI_SERVER_HOST = "timesafari.app",
|
|
||||||
PROD_TIME_SAFARI_SERVER = "https://" + PROD_TIME_SAFARI_SERVER_HOST,
|
|
||||||
|
|
||||||
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
||||||
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
|
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
|
||||||
LOCAL_ENDORSER_API_SERVER = "http://localhost:3000",
|
LOCAL_ENDORSER_API_SERVER = "http://localhost:3000",
|
||||||
|
|
||||||
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
|
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
|
||||||
PROD_PUSH_SERVER = "https://timesafari.app",
|
PROD_PUSH_SERVER = "https://timesafari.app",
|
||||||
TEST1_PUSH_SERVER = "https://test.timesafari.app",
|
TEST1_PUSH_SERVER = "https://test.timesafari.app",
|
||||||
TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com",
|
TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com",
|
||||||
|
|||||||
@@ -45,5 +45,8 @@ db.on("populate", () => {
|
|||||||
db.settings.add({
|
db.settings.add({
|
||||||
id: MASTER_SETTINGS_KEY,
|
id: MASTER_SETTINGS_KEY,
|
||||||
apiServer: AppString.DEFAULT_ENDORSER_API_SERVER,
|
apiServer: AppString.DEFAULT_ENDORSER_API_SERVER,
|
||||||
|
|
||||||
|
// remember that things you add from now on aren't automatically in the DB for old users
|
||||||
|
webPushServer: AppString.DEFAULT_PUSH_SERVER,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,17 +12,15 @@ export type BoundingBox = {
|
|||||||
* Settings type encompasses user-specific configuration details.
|
* Settings type encompasses user-specific configuration details.
|
||||||
*/
|
*/
|
||||||
export type Settings = {
|
export type Settings = {
|
||||||
id: number; // Only one entry, keyed with MASTER_SETTINGS_KEY
|
id: number; // Only one entry using MASTER_SETTINGS_KEY
|
||||||
|
|
||||||
activeDid?: string; // Active Decentralized ID
|
activeDid?: string; // Active Decentralized ID
|
||||||
apiServer?: string; // API server URL
|
apiServer?: string; // API server URL
|
||||||
firstName?: string; // User's first name
|
firstName?: string; // User's first name
|
||||||
isRegistered?: boolean;
|
lastName?: string; // User's last name
|
||||||
lastName?: string; // deprecated - put all names in firstName
|
|
||||||
lastNotifiedClaimId?: string; // Last notified claim ID
|
|
||||||
lastViewedClaimId?: string; // Last viewed claim ID
|
lastViewedClaimId?: string; // Last viewed claim ID
|
||||||
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
|
lastNotifiedClaimId?: string; // Last notified claim ID
|
||||||
reminderOn?: boolean; // Toggle to enable or disable reminders
|
isRegistered?: boolean;
|
||||||
|
webPushServer?: string; // Web Push server URL
|
||||||
|
|
||||||
// Array of named search boxes defined by bounding boxes
|
// Array of named search boxes defined by bounding boxes
|
||||||
searchBoxes?: Array<{
|
searchBoxes?: Array<{
|
||||||
@@ -32,9 +30,8 @@ export type Settings = {
|
|||||||
|
|
||||||
showContactGivesInline?: boolean; // Display contact inline or not
|
showContactGivesInline?: boolean; // Display contact inline or not
|
||||||
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
|
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
|
||||||
warnIfProdServer?: boolean; // Warn if using a production server
|
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
|
||||||
warnIfTestServer?: boolean; // Warn if using a testing server
|
reminderOn?: boolean; // Toggle to enable or disable reminders
|
||||||
webPushServer?: string; // Web Push server URL
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ export const SERVICE_ID = "endorser.ch";
|
|||||||
export const CONTACT_URL_PREFIX = "https://endorser.ch";
|
export const CONTACT_URL_PREFIX = "https://endorser.ch";
|
||||||
// the suffix for the contact URL
|
// the suffix for the contact URL
|
||||||
export const ENDORSER_JWT_URL_LOCATION = "/contact?jwt=";
|
export const ENDORSER_JWT_URL_LOCATION = "/contact?jwt=";
|
||||||
// the prefix for handle IDs, the permanent ID for claims on Endorser
|
|
||||||
export const ENDORSER_CH_HANDLE_PREFIX = "https://endorser.ch/entity/";
|
|
||||||
|
|
||||||
export interface AgreeVerifiableCredential {
|
export interface AgreeVerifiableCredential {
|
||||||
"@context": string;
|
"@context": string;
|
||||||
@@ -40,24 +38,14 @@ export interface ClaimResult {
|
|||||||
error: { code: string; message: string };
|
error: { code: string; message: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericVerifiableCredential {
|
export interface GenericClaim {
|
||||||
"@context": string;
|
"@context": string;
|
||||||
"@type": string;
|
"@type": string;
|
||||||
}
|
issuedAt: string;
|
||||||
|
// "any" because arbitrary objects can be subject of agreement
|
||||||
export interface GenericServerRecord extends GenericVerifiableCredential {
|
|
||||||
handleId?: string;
|
|
||||||
id?: string;
|
|
||||||
issuedAt?: string;
|
|
||||||
issuer?: string;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
claim: Record<any, any>;
|
claim: Record<any, any>;
|
||||||
}
|
}
|
||||||
export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = {
|
|
||||||
"@context": SCHEMA_ORG_CONTEXT,
|
|
||||||
"@type": "",
|
|
||||||
claim: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface GiveServerRecord {
|
export interface GiveServerRecord {
|
||||||
agentDid: string;
|
agentDid: string;
|
||||||
@@ -155,104 +143,6 @@ export function isHiddenDid(did: string) {
|
|||||||
return did === HIDDEN_DID;
|
return did === HIDDEN_DID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true for any nested string where func(input) === true
|
|
||||||
*
|
|
||||||
* Similar logic is found in endorser-mobile.
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
function testRecursivelyOnString(func: (arg0: any) => boolean, input: any) {
|
|
||||||
if (Object.prototype.toString.call(input) === "[object String]") {
|
|
||||||
return func(input);
|
|
||||||
} else if (input instanceof Object) {
|
|
||||||
if (!Array.isArray(input)) {
|
|
||||||
// it's an object
|
|
||||||
for (const key in input) {
|
|
||||||
if (testRecursivelyOnString(func, input[key])) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// it's an array
|
|
||||||
for (const value of input) {
|
|
||||||
if (testRecursivelyOnString(func, value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export function containsHiddenDid(obj: any) {
|
|
||||||
return testRecursivelyOnString(isHiddenDid, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stripEndorserPrefix(claimId: string) {
|
|
||||||
if (claimId && claimId.startsWith(ENDORSER_CH_HANDLE_PREFIX)) {
|
|
||||||
return claimId.substring(ENDORSER_CH_HANDLE_PREFIX.length);
|
|
||||||
} else {
|
|
||||||
return claimId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// similar logic is found in endorser-mobile
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export function removeSchemaContext(obj: any) {
|
|
||||||
return obj["@context"] === SCHEMA_ORG_CONTEXT
|
|
||||||
? R.omit(["@context"], obj)
|
|
||||||
: obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
// similar logic is found in endorser-mobile
|
|
||||||
export function addLastClaimOrHandleAsIdIfMissing(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
obj: any,
|
|
||||||
lastClaimId?: string,
|
|
||||||
handleId?: string,
|
|
||||||
) {
|
|
||||||
if (!obj.identifier && lastClaimId) {
|
|
||||||
const result = R.clone(obj);
|
|
||||||
result.lastClaimId = lastClaimId;
|
|
||||||
return result;
|
|
||||||
} else if (!obj.identifier && handleId) {
|
|
||||||
const result = R.clone(obj);
|
|
||||||
result.identifier = handleId;
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return clone of object without any nested *VisibleToDids keys
|
|
||||||
// similar logic is found in endorser-mobile
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export function removeVisibleToDids(input: any): any {
|
|
||||||
if (input instanceof Object) {
|
|
||||||
if (!Array.isArray(input)) {
|
|
||||||
// it's an object
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const result: Record<string, any> = {};
|
|
||||||
for (const key in input) {
|
|
||||||
if (!key.endsWith("VisibleToDids")) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
result[key] = removeVisibleToDids(R.clone(input[key]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
// it's an array
|
|
||||||
return R.map(removeVisibleToDids, input);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY
|
always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY
|
||||||
|
|
||||||
@@ -310,7 +200,6 @@ export async function createAndSubmitGive(
|
|||||||
toDid?: string,
|
toDid?: string,
|
||||||
description?: string,
|
description?: string,
|
||||||
hours?: number,
|
hours?: number,
|
||||||
unitCode?: string,
|
|
||||||
fulfillsProjectHandleId?: string,
|
fulfillsProjectHandleId?: string,
|
||||||
): Promise<CreateAndSubmitClaimResult> {
|
): Promise<CreateAndSubmitClaimResult> {
|
||||||
const vcClaim: GiveVerifiableCredential = {
|
const vcClaim: GiveVerifiableCredential = {
|
||||||
@@ -319,15 +208,13 @@ export async function createAndSubmitGive(
|
|||||||
recipient: toDid ? { identifier: toDid } : undefined,
|
recipient: toDid ? { identifier: toDid } : undefined,
|
||||||
agent: fromDid ? { identifier: fromDid } : undefined,
|
agent: fromDid ? { identifier: fromDid } : undefined,
|
||||||
description: description || undefined,
|
description: description || undefined,
|
||||||
object: hours
|
object: hours ? { amountOfThisGood: hours, unitCode: "HUR" } : undefined,
|
||||||
? { amountOfThisGood: hours, unitCode: unitCode || "HUR" }
|
|
||||||
: undefined,
|
|
||||||
fulfills: fulfillsProjectHandleId
|
fulfills: fulfillsProjectHandleId
|
||||||
? { "@type": "PlanAction", identifier: fulfillsProjectHandleId }
|
? { "@type": "PlanAction", identifier: fulfillsProjectHandleId }
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
return createAndSubmitClaim(
|
return createAndSubmitClaim(
|
||||||
vcClaim as GenericServerRecord,
|
vcClaim as GenericClaim,
|
||||||
identity,
|
identity,
|
||||||
apiServer,
|
apiServer,
|
||||||
axios,
|
axios,
|
||||||
@@ -375,7 +262,7 @@ export async function createAndSubmitOffer(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return createAndSubmitClaim(
|
return createAndSubmitClaim(
|
||||||
vcClaim as GenericServerRecord,
|
vcClaim as GenericClaim,
|
||||||
identity,
|
identity,
|
||||||
apiServer,
|
apiServer,
|
||||||
axios,
|
axios,
|
||||||
@@ -383,7 +270,7 @@ export async function createAndSubmitOffer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createAndSubmitClaim(
|
export async function createAndSubmitClaim(
|
||||||
vcClaim: GenericVerifiableCredential,
|
vcClaim: GenericClaim,
|
||||||
identity: IIdentifier,
|
identity: IIdentifier,
|
||||||
apiServer: string,
|
apiServer: string,
|
||||||
axios: Axios,
|
axios: Axios,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// many of these are also found in endorser-mobile utility.ts
|
|
||||||
|
|
||||||
export const isGlobalUri = (uri: string) => {
|
export const isGlobalUri = (uri: string) => {
|
||||||
return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/));
|
return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import {
|
|||||||
faLongArrowAltLeft,
|
faLongArrowAltLeft,
|
||||||
faLongArrowAltRight,
|
faLongArrowAltRight,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faMessage,
|
|
||||||
faPen,
|
faPen,
|
||||||
faPersonCircleCheck,
|
faPersonCircleCheck,
|
||||||
faPersonCircleQuestion,
|
faPersonCircleQuestion,
|
||||||
@@ -95,7 +94,6 @@ library.add(
|
|||||||
faLongArrowAltLeft,
|
faLongArrowAltLeft,
|
||||||
faLongArrowAltRight,
|
faLongArrowAltRight,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faMessage,
|
|
||||||
faPen,
|
faPen,
|
||||||
faPersonCircleCheck,
|
faPersonCircleCheck,
|
||||||
faPersonCircleQuestion,
|
faPersonCircleQuestion,
|
||||||
|
|||||||
@@ -39,12 +39,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "account",
|
name: "account",
|
||||||
component: () =>
|
component: () =>
|
||||||
import(/* webpackChunkName: "account" */ "../views/AccountViewView.vue"),
|
import(/* webpackChunkName: "account" */ "../views/AccountViewView.vue"),
|
||||||
},
|
beforeEnter: enterOrStart,
|
||||||
{
|
|
||||||
path: "/claim/:id?",
|
|
||||||
name: "claim",
|
|
||||||
component: () =>
|
|
||||||
import(/* webpackChunkName: "claim" */ "../views/ClaimView.vue"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/confirm-contact",
|
path: "/confirm-contact",
|
||||||
|
|||||||
2184
src/util.d.ts
vendored
2184
src/util.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<QuickNav selected="Profile"></QuickNav>
|
<QuickNav selected="Profile"></QuickNav>
|
||||||
<TopMessage />
|
|
||||||
|
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="p-6 pb-24">
|
<section id="Content" class="p-6 pb-24">
|
||||||
<!-- Heading -->
|
<!-- Heading -->
|
||||||
@@ -34,24 +32,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ID notice -->
|
|
||||||
<div
|
|
||||||
v-if="!activeDid"
|
|
||||||
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
|
|
||||||
>
|
|
||||||
<p class="mb-4">
|
|
||||||
<b>Note:</b> Before you can take any action, you need an ID.
|
|
||||||
</p>
|
|
||||||
<router-link
|
|
||||||
:to="{ name: 'start' }"
|
|
||||||
class="inline-block text-md uppercase bg-amber-600 text-white px-4 py-2 rounded-md"
|
|
||||||
>
|
|
||||||
Generate Identity
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Registration notice -->
|
<!-- Registration notice -->
|
||||||
<!-- We won't show any loading indicator because it usually doesn't change anything. We'll just pop the message in only if we discover that they need it. -->
|
<!-- We won't show any loading indicator; we'll just pop the message in once we know they need it. -->
|
||||||
<div
|
<div
|
||||||
v-if="!loadingLimits && !limits?.nextWeekBeginDateTime"
|
v-if="!loadingLimits && !limits?.nextWeekBeginDateTime"
|
||||||
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
|
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
|
||||||
@@ -76,9 +58,9 @@
|
|||||||
<span v-else>
|
<span v-else>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'new-edit-account' }"
|
:to="{ name: 'new-edit-account' }"
|
||||||
class="block w-full text-center text-md text-slate-500 uppercase border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
|
class="text-xs bg-blue-500 text-white px-1.5 py-1 rounded-md"
|
||||||
>
|
>
|
||||||
(Set Your Name)
|
(set name)
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -106,25 +88,16 @@
|
|||||||
|
|
||||||
<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
|
<label
|
||||||
v-if="notificationUnchanged"
|
|
||||||
for="toggleNotifications"
|
for="toggleNotifications"
|
||||||
class="flex items-center justify-between cursor-pointer"
|
class="flex items-center cursor-pointer"
|
||||||
@click="
|
@click="
|
||||||
!toggleNotifications
|
this.$notify(
|
||||||
? this.$notify(
|
{
|
||||||
{
|
group: 'modal',
|
||||||
group: 'modal',
|
type: 'notification-permission',
|
||||||
type: 'notification-permission',
|
},
|
||||||
},
|
-1,
|
||||||
-1,
|
)
|
||||||
)
|
|
||||||
: this.$notify(
|
|
||||||
{
|
|
||||||
group: 'modal',
|
|
||||||
type: 'notification-off',
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<!-- label -->
|
<!-- label -->
|
||||||
@@ -146,9 +119,37 @@
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label v-else>
|
<label
|
||||||
Notification status may have changed. Revisit this page to see the
|
for="toggleMuteNotifications"
|
||||||
latest setting.
|
class="flex items-center cursor-pointer mt-4"
|
||||||
|
@click="
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: 'modal',
|
||||||
|
type: 'notification-mute',
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- label -->
|
||||||
|
<div>Mute Notifications</div>
|
||||||
|
<!-- toggle -->
|
||||||
|
<div class="relative ml-2">
|
||||||
|
<!-- input -->
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="toggleMuteNotifications"
|
||||||
|
class="sr-only"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<!-- line -->
|
||||||
|
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
||||||
|
<!-- dot -->
|
||||||
|
<div
|
||||||
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@
|
|||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'seed-backup' }"
|
:to="{ name: 'seed-backup' }"
|
||||||
v-if="activeDid"
|
href=""
|
||||||
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||||
>
|
>
|
||||||
Backup Identifier Seed
|
Backup Identifier Seed
|
||||||
@@ -171,42 +172,30 @@
|
|||||||
</a>
|
</a>
|
||||||
<a ref="downloadLink" />
|
<a ref="downloadLink" />
|
||||||
|
|
||||||
<div v-if="activeDid" class="my-8">
|
<div v-if="activeDid" class="flex py-2">
|
||||||
<h3 class="text-sm uppercase font-semibold mb-3">Rate Limits</h3>
|
<button class="text-center text-md text-blue-500" @click="checkLimits()">
|
||||||
<button
|
|
||||||
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
|
||||||
@click="checkLimits()"
|
|
||||||
>
|
|
||||||
Check Limits
|
Check Limits
|
||||||
</button>
|
</button>
|
||||||
<!-- show spinner if loading limits -->
|
<!-- show spinner if loading limits -->
|
||||||
<div v-if="loadingLimits" class="text-center mb-4">
|
<div v-if="loadingLimits" class="ml-2">
|
||||||
Checking… <fa icon="spinner" class="fa-spin"></fa>
|
Checking... <fa icon="spinner" class="fa-spin"></fa>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="ml-2">
|
||||||
{{ limitsMessage }}
|
{{ limitsMessage }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!!limits?.nextWeekBeginDateTime">
|
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9">
|
||||||
<p class="mb-3 text-sm">
|
<span class="font-bold">Rate Limits</span>
|
||||||
You have done <b>{{ limits.doneClaimsThisWeek }}</b> claims out of
|
<p>
|
||||||
<b>{{ limits.maxClaimsPerWeek }}</b> for this week. Your claims
|
You have done {{ limits.doneClaimsThisWeek }} claims out of
|
||||||
counter resets at
|
{{ limits.maxClaimsPerWeek }} for this week. Your claims counter
|
||||||
<b class="whitespace-nowrap">{{
|
resets at {{ readableTime(limits.nextWeekBeginDateTime) }}
|
||||||
readableTime(limits.nextWeekBeginDateTime)
|
|
||||||
}}</b>
|
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm">
|
<p>
|
||||||
You have done
|
You have done {{ limits.doneRegistrationsThisMonth }} registrations
|
||||||
<b>{{ limits.doneRegistrationsThisMonth }}</b> registrations out of
|
out of {{ limits.maxRegistrationsPerMonth }} for this month. (You can
|
||||||
<b>{{ limits.maxRegistrationsPerMonth }}</b> for this month.
|
register nobody on your first day, and after that only one a day in
|
||||||
<i
|
your first month.) Your registration counter resets at
|
||||||
>(You can register nobody on your first day, and after that only one
|
{{ readableTime(limits.nextMonthBeginDateTime) }}
|
||||||
a day in your first month.)</i
|
|
||||||
>
|
|
||||||
Your registration counter resets at
|
|
||||||
<b class="whitespace-nowrap">
|
|
||||||
{{ readableTime(limits.nextMonthBeginDateTime) }}
|
|
||||||
</b>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,14 +208,10 @@
|
|||||||
>
|
>
|
||||||
Advanced
|
Advanced
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="showAdvanced">
|
|
||||||
<p class="text-rose-600 mb-8">
|
|
||||||
Beware: the features here can be confusing and even change data in ways
|
|
||||||
you do not expect. But we support your freedom!
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
<div v-if="showAdvanced">
|
||||||
<!-- Deep Identity Details -->
|
<!-- Deep Identity Details -->
|
||||||
<h2 class="text-sm uppercase font-semibold mb-3">
|
<h2 class="text-slate-500 text-sm font-bold mb-2 py-2">
|
||||||
Deep Identity Details
|
Deep Identity Details
|
||||||
</h2>
|
</h2>
|
||||||
<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">
|
||||||
@@ -284,11 +269,13 @@
|
|||||||
|
|
||||||
<label
|
<label
|
||||||
for="toggleShowAmounts"
|
for="toggleShowAmounts"
|
||||||
class="flex items-center justify-between cursor-pointer my-4"
|
class="flex items-center cursor-pointer py-2"
|
||||||
@click="handleChange"
|
@click="handleChange"
|
||||||
>
|
>
|
||||||
<!-- label -->
|
<!-- label -->
|
||||||
<h2>Show amounts given with contacts</h2>
|
<h2 class="text-slate-500 text-sm font-bold mb-2">
|
||||||
|
Show amounts given with contacts
|
||||||
|
</h2>
|
||||||
<!-- toggle -->
|
<!-- toggle -->
|
||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
@@ -307,28 +294,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<router-link
|
<div class="flex py-2">
|
||||||
:to="{ name: 'statistics' }"
|
<button class="text-blue-500">
|
||||||
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
<router-link :to="{ name: 'statistics' }" class="block text-center">
|
||||||
>
|
See Global Animated History of Giving
|
||||||
See Global Animated History of Giving
|
</router-link>
|
||||||
</router-link>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- id used by puppeteer test script -->
|
<div class="flex py-2">
|
||||||
<router-link
|
<button class="text-blue-500">
|
||||||
id="switch-identity-link"
|
<!-- id used by puppeteer test script -->
|
||||||
:to="{ name: 'identity-switcher' }"
|
<router-link
|
||||||
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
id="switch-identity-link"
|
||||||
>
|
:to="{ name: 'identity-switcher' }"
|
||||||
Switch Identity
|
class="block text-center"
|
||||||
</router-link>
|
>
|
||||||
|
Switch Identity / No Identity
|
||||||
<button
|
</router-link>
|
||||||
@click="alertWebPushSubscription()"
|
</button>
|
||||||
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
</div>
|
||||||
>
|
|
||||||
Show Subscription from Web Push Server
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="flex py-4">
|
<div class="flex py-4">
|
||||||
<h2 class="text-slate-500 text-sm font-bold mb-2">Claim Server</h2>
|
<h2 class="text-slate-500 text-sm font-bold mb-2">Claim Server</h2>
|
||||||
@@ -364,46 +349,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label
|
|
||||||
for="toggleProdWarningMessage"
|
|
||||||
class="flex items-center justify-between cursor-pointer my-4"
|
|
||||||
@click="toggleProdWarning"
|
|
||||||
>
|
|
||||||
<!-- label -->
|
|
||||||
<h2>Show warning if on prod server</h2>
|
|
||||||
<!-- toggle -->
|
|
||||||
<div class="relative ml-2">
|
|
||||||
<!-- input -->
|
|
||||||
<input type="checkbox" v-model="warnIfProdServer" class="sr-only" />
|
|
||||||
<!-- line -->
|
|
||||||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
|
||||||
<!-- dot -->
|
|
||||||
<div
|
|
||||||
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label
|
|
||||||
for="toggleTestWarningMessage"
|
|
||||||
class="flex items-center justify-between cursor-pointer my-4"
|
|
||||||
@click="toggleTestWarning"
|
|
||||||
>
|
|
||||||
<!-- label -->
|
|
||||||
<h2>Show warning if on test server</h2>
|
|
||||||
<!-- toggle -->
|
|
||||||
<div class="relative ml-2">
|
|
||||||
<!-- input -->
|
|
||||||
<input type="checkbox" v-model="warnIfTestServer" class="sr-only" />
|
|
||||||
<!-- line -->
|
|
||||||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
|
||||||
<!-- dot -->
|
|
||||||
<div
|
|
||||||
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="flex py-4">
|
<div class="flex py-4">
|
||||||
<h2 class="text-slate-500 text-sm font-bold mb-2">
|
<h2 class="text-slate-500 text-sm font-bold mb-2">
|
||||||
Notification Push Server
|
Notification Push Server
|
||||||
@@ -455,7 +400,6 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
|
||||||
import { AppString } from "@/constants/app";
|
import { AppString } from "@/constants/app";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
@@ -480,7 +424,7 @@ interface IAccount {
|
|||||||
derivationPath: string;
|
derivationPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({ components: { QuickNav, TopMessage } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class AccountViewView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||||||
|
|
||||||
@@ -492,7 +436,6 @@ export default class AccountViewView extends Vue {
|
|||||||
derivationPath = "";
|
derivationPath = "";
|
||||||
givenName = "";
|
givenName = "";
|
||||||
isRegistered = false;
|
isRegistered = false;
|
||||||
notificationUnchanged = true;
|
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
publicHex = "";
|
publicHex = "";
|
||||||
publicBase64 = "";
|
publicBase64 = "";
|
||||||
@@ -510,73 +453,14 @@ export default class AccountViewView extends Vue {
|
|||||||
|
|
||||||
showAdvanced = false;
|
showAdvanced = false;
|
||||||
|
|
||||||
subscription: PushSubscription | null = null;
|
|
||||||
warnIfProdServer = false;
|
|
||||||
warnIfTestServer = false;
|
|
||||||
|
|
||||||
private isSubscribed = false;
|
private isSubscribed = false;
|
||||||
|
|
||||||
get toggleNotifications() {
|
get toggleNotifications() {
|
||||||
return this.isSubscribed;
|
return this.isSubscribed;
|
||||||
}
|
}
|
||||||
|
|
||||||
set toggleNotifications(value) {
|
set toggleNotifications(value) {
|
||||||
this.isSubscribed = value;
|
this.isSubscribed = value;
|
||||||
this.notificationUnchanged = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Async function executed when the component is created.
|
|
||||||
* Initializes the component's state with values from the database,
|
|
||||||
* handles identity-related tasks, and checks limitations.
|
|
||||||
*
|
|
||||||
* @throws Will display specific messages to the user based on different errors.
|
|
||||||
*/
|
|
||||||
async created() {
|
|
||||||
try {
|
|
||||||
await db.open();
|
|
||||||
|
|
||||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
|
||||||
|
|
||||||
// Initialize component state with values from the database or defaults
|
|
||||||
this.initializeState(settings);
|
|
||||||
|
|
||||||
// Get and process the identity
|
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
|
||||||
if (identity) {
|
|
||||||
this.processIdentity(identity);
|
|
||||||
}
|
|
||||||
} catch (err: unknown) {
|
|
||||||
this.handleError(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async mounted() {
|
|
||||||
try {
|
|
||||||
const registration = await navigator.serviceWorker.ready;
|
|
||||||
this.subscription = await registration.pushManager.getSubscription();
|
|
||||||
this.toggleNotifications = !!this.subscription;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Mount error:", error);
|
|
||||||
this.toggleNotifications = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes component state with values from the database or defaults.
|
|
||||||
* @param {SettingsType} settings - Object containing settings from the database.
|
|
||||||
*/
|
|
||||||
initializeState(settings: Settings | undefined) {
|
|
||||||
this.activeDid = (settings?.activeDid as string) || "";
|
|
||||||
this.apiServer = (settings?.apiServer as string) || "";
|
|
||||||
this.apiServerInput = (settings?.apiServer as string) || "";
|
|
||||||
this.givenName =
|
|
||||||
(settings?.firstName || "") +
|
|
||||||
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
|
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
|
||||||
this.showContactGives = !!settings?.showContactGivesInline;
|
|
||||||
this.warnIfProdServer = !!settings?.warnIfProdServer;
|
|
||||||
this.warnIfTestServer = !!settings?.warnIfTestServer;
|
|
||||||
this.webPushServer = (settings?.webPushServer as string) || "";
|
|
||||||
this.webPushServerInput = (settings?.webPushServer as string) || "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
|
public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
|
||||||
@@ -644,16 +528,6 @@ export default class AccountViewView extends Vue {
|
|||||||
this.updateShowContactAmounts();
|
this.updateShowContactAmounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleProdWarning() {
|
|
||||||
this.warnIfProdServer = !this.warnIfProdServer;
|
|
||||||
this.updateWarnIfProdServer(this.warnIfProdServer);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleTestWarning() {
|
|
||||||
this.warnIfTestServer = !this.warnIfTestServer;
|
|
||||||
this.updateWarnIfTestServer(this.warnIfTestServer);
|
|
||||||
}
|
|
||||||
|
|
||||||
readableTime(timeStr: string) {
|
readableTime(timeStr: string) {
|
||||||
return timeStr.substring(0, timeStr.indexOf("T"));
|
return timeStr.substring(0, timeStr.indexOf("T"));
|
||||||
}
|
}
|
||||||
@@ -663,6 +537,62 @@ export default class AccountViewView extends Vue {
|
|||||||
this.numAccounts = await accountsDB.accounts.count();
|
this.numAccounts = await accountsDB.accounts.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async function executed when the component is created.
|
||||||
|
* Initializes the component's state with values from the database,
|
||||||
|
* handles identity-related tasks, and checks limitations.
|
||||||
|
*
|
||||||
|
* @throws Will display specific messages to the user based on different errors.
|
||||||
|
*/
|
||||||
|
async created() {
|
||||||
|
console.error("created");
|
||||||
|
try {
|
||||||
|
await db.open();
|
||||||
|
|
||||||
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
|
|
||||||
|
// Initialize component state with values from the database or defaults
|
||||||
|
this.initializeState(settings);
|
||||||
|
|
||||||
|
// Get and process the identity
|
||||||
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
|
if (identity) {
|
||||||
|
this.processIdentity(identity);
|
||||||
|
}
|
||||||
|
} catch (err: unknown) {
|
||||||
|
this.handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
console.error("mounted()");
|
||||||
|
try {
|
||||||
|
const registration = await navigator.serviceWorker.ready;
|
||||||
|
const subscription = await registration.pushManager.getSubscription();
|
||||||
|
this.toggleNotifications = !!subscription;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
this.toggleNotifications = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes component state with values from the database or defaults.
|
||||||
|
* @param {SettingsType} settings - Object containing settings from the database.
|
||||||
|
*/
|
||||||
|
initializeState(settings: Settings | undefined) {
|
||||||
|
this.activeDid = (settings?.activeDid as string) || "";
|
||||||
|
this.apiServer = (settings?.apiServer as string) || "";
|
||||||
|
this.apiServerInput = (settings?.apiServer as string) || "";
|
||||||
|
this.givenName =
|
||||||
|
(settings?.firstName || "") +
|
||||||
|
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
|
||||||
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
this.webPushServer = (settings?.webPushServer as string) || "";
|
||||||
|
this.webPushServerInput = (settings?.webPushServer as string) || "";
|
||||||
|
this.showContactGives = !!settings?.showContactGivesInline;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes the identity and updates the component's state.
|
* Processes the identity and updates the component's state.
|
||||||
* @param {IdentityType} identity - Object containing identity information.
|
* @param {IdentityType} identity - Object containing identity information.
|
||||||
@@ -736,52 +666,6 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateWarnIfProdServer(newSetting: boolean) {
|
|
||||||
try {
|
|
||||||
await db.open();
|
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
|
||||||
warnIfProdServer: newSetting,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error Updating Prod Warning",
|
|
||||||
text: "Clear your cache and start over (after data backup).",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
console.error(
|
|
||||||
"Telling user to clear cache after contact setting update because:",
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateWarnIfTestServer(newSetting: boolean) {
|
|
||||||
try {
|
|
||||||
await db.open();
|
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
|
||||||
warnIfTestServer: newSetting,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error Updating Test Warning",
|
|
||||||
text: "Clear your cache and start over (after data backup).",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
console.error(
|
|
||||||
"Telling user to clear cache after contact setting update because:",
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously exports the database into a downloadable JSON file.
|
* Asynchronously exports the database into a downloadable JSON file.
|
||||||
*
|
*
|
||||||
@@ -1039,23 +923,6 @@ export default class AccountViewView extends Vue {
|
|||||||
webPushServer: this.webPushServerInput,
|
webPushServer: this.webPushServerInput,
|
||||||
});
|
});
|
||||||
this.webPushServer = this.webPushServerInput;
|
this.webPushServer = this.webPushServerInput;
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "warning",
|
|
||||||
title: "Reload",
|
|
||||||
text: "Now reload the app to get a new VAPID to use with this push server.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
alertWebPushSubscription() {
|
|
||||||
console.log(
|
|
||||||
"Web push subscription:",
|
|
||||||
JSON.parse(JSON.stringify(this.subscription)), // gives more info than plain console logging
|
|
||||||
);
|
|
||||||
alert(JSON.stringify(this.subscription));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,476 +0,0 @@
|
|||||||
<template>
|
|
||||||
<QuickNav />
|
|
||||||
<!-- CONTENT -->
|
|
||||||
<section id="Content" class="p-6 pb-24">
|
|
||||||
<!-- Breadcrumb -->
|
|
||||||
<div id="ViewBreadcrumb" class="mb-8">
|
|
||||||
<h1 class="text-lg text-center font-light relative px-7">
|
|
||||||
<!-- Back -->
|
|
||||||
<button
|
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
|
||||||
>
|
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
|
||||||
</button>
|
|
||||||
Verifiable Claim Details
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Details -->
|
|
||||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
|
||||||
<div>
|
|
||||||
<div class="block flex gap-4 overflow-hidden">
|
|
||||||
<div class="overflow-hidden">
|
|
||||||
<h2 class="text-md font-bold">{{ veriClaim.id }}</h2>
|
|
||||||
<div class="text-sm">
|
|
||||||
<div>
|
|
||||||
{{ veriClaim.claimType }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<fa icon="message" class="fa-fw text-slate-400"></fa>
|
|
||||||
{{ veriClaim.claim?.description }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
|
||||||
{{ veriClaim.issuer }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<fa icon="calendar" class="fa-fw text-slate-400"></fa>
|
|
||||||
{{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Confirmations</h2>
|
|
||||||
|
|
||||||
<span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span>
|
|
||||||
<span v-else-if="totalConfirmers() === 1">
|
|
||||||
One person has confirmed this.
|
|
||||||
</span>
|
|
||||||
<span v-else> {{ totalConfirmers() }} people have confirmed this. </span>
|
|
||||||
|
|
||||||
<div v-if="totalConfirmers() > 0">
|
|
||||||
<div
|
|
||||||
v-if="
|
|
||||||
confirmerIdList.length === 0 && confsVisibleToIdList.length === 0
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Nobody that you know confirmed this claim, nor do they have any
|
|
||||||
confirmers in their network.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="confirmerIdList.length === 0 && confsVisibleToIdList.length > 0"
|
|
||||||
>
|
|
||||||
<!-- Only show if this person has links to confirmers (below). -->
|
|
||||||
Nobody that you know has issued or confirmed this claim.
|
|
||||||
</div>
|
|
||||||
<div v-if="confirmerIdList.length > 0">
|
|
||||||
The following people have issued or confirmed this claim.
|
|
||||||
<ul>
|
|
||||||
<li
|
|
||||||
v-for="confirmerId in confirmerIdList"
|
|
||||||
:key="confirmerId"
|
|
||||||
class="list-disc"
|
|
||||||
>
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<div class="grow overflow-hidden">
|
|
||||||
<div class="text-sm">
|
|
||||||
{{ confirmerId }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Never need to show the following message.
|
|
||||||
If there is nobody in the confirmerIdList then we'll show the combined "nobody" message.
|
|
||||||
If there is somebody in the confirmerIdList then that's all they need to show.
|
|
||||||
-->
|
|
||||||
<!-- Nobody that you know can see someone who has confirmed this claim. -->
|
|
||||||
|
|
||||||
<div v-if="confsVisibleToIdList.length > 0">
|
|
||||||
The following people can connect you with people who have issued or
|
|
||||||
confirmed this claim.
|
|
||||||
<ul>
|
|
||||||
<li
|
|
||||||
v-for="confsVisibleTo in confsVisibleToIdList"
|
|
||||||
:key="confsVisibleTo"
|
|
||||||
class="list-disc"
|
|
||||||
>
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<div class="grow overflow-hidden">
|
|
||||||
<div class="text-sm">
|
|
||||||
{{ confsVisibleTo }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<div v-if="confirmerIdList.includes(activeDid)">
|
|
||||||
You have confirmed this claim.
|
|
||||||
</div>
|
|
||||||
<div v-else-if="containsHiddenDid(veriClaim.claim)">
|
|
||||||
You cannot confirm this claim because it contains data that is hidden
|
|
||||||
from you.
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<button
|
|
||||||
class="bg-blue-600 text-white mt-4 px-4 py-2 rounded-md mb-4"
|
|
||||||
@click="confirmClaim(veriClaim.id)"
|
|
||||||
>
|
|
||||||
Confirm Claim
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Claim</h2>
|
|
||||||
<pre class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md">
|
|
||||||
{{ util.inspect(veriClaim, false, null) }}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Full Claim</h2>
|
|
||||||
<p class="mb-4">
|
|
||||||
The full claim includes the claim as it was originally issued, including
|
|
||||||
the signature (ie. the proof of issuance by that person).
|
|
||||||
</p>
|
|
||||||
<div v-if="!fullClaim">
|
|
||||||
<p v-if="fullClaimMessage" class="mb-4">
|
|
||||||
{{ fullClaimMessage }}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
v-else
|
|
||||||
class="block w-full text-center text-md uppercase bg-blue-600 text-white px-1.5 py-2 rounded-md mb-2"
|
|
||||||
@click="showFullClaim(veriClaim.id)"
|
|
||||||
>
|
|
||||||
Load Full Claim Details
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<pre>{{ util.inspect(fullClaim, false, null) }}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a
|
|
||||||
:href="apiServer + '/api/claim/' + veriClaim.id"
|
|
||||||
target="_blank"
|
|
||||||
class="block w-full text-center text-md uppercase bg-blue-600 text-white px-1.5 py-2 rounded-md mb-2"
|
|
||||||
>
|
|
||||||
View on the Public Server
|
|
||||||
</a>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { AxiosError, RawAxiosRequestHeaders } from "axios";
|
|
||||||
import * as R from "ramda";
|
|
||||||
import { IIdentifier } from "@veramo/core";
|
|
||||||
import * as util from "util";
|
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
|
||||||
|
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
|
||||||
import OfferDialog from "@/components/OfferDialog.vue";
|
|
||||||
import { accountsDB, db } from "@/db/index";
|
|
||||||
import { Contact } from "@/db/tables/contacts";
|
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
|
||||||
import { accessToken } from "@/libs/crypto";
|
|
||||||
import * as serverUtil from "@/libs/endorserServer";
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
|
||||||
import { Account } from "@/db/tables/accounts";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav },
|
|
||||||
})
|
|
||||||
export default class ClaimView extends Vue {
|
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
activeDid = "";
|
|
||||||
allMyDids: Array<string> = [];
|
|
||||||
allContacts: Array<Contact> = [];
|
|
||||||
apiServer = "";
|
|
||||||
confirmerIdList = []; // list of DIDs that have confirmed this claim excluding the issuer
|
|
||||||
confsVisibleErrorMessage = "";
|
|
||||||
confsVisibleToIdList = []; // list of DIDs that can see any confirmer
|
|
||||||
fullClaim = null;
|
|
||||||
fullClaimMessage = "";
|
|
||||||
numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible
|
|
||||||
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
|
||||||
|
|
||||||
util = util;
|
|
||||||
containsHiddenDid = serverUtil.containsHiddenDid;
|
|
||||||
|
|
||||||
async created() {
|
|
||||||
await db.open();
|
|
||||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
|
||||||
this.activeDid = settings?.activeDid || "";
|
|
||||||
this.apiServer = settings?.apiServer || "";
|
|
||||||
this.allContacts = await db.contacts.toArray();
|
|
||||||
|
|
||||||
await accountsDB.open();
|
|
||||||
const accounts = accountsDB.accounts;
|
|
||||||
const accountsArr = await accounts?.toArray();
|
|
||||||
this.allMyDids = accountsArr.map((acc) => acc.did);
|
|
||||||
const account = accountsArr.find((acc) => acc.did === this.activeDid);
|
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
|
||||||
|
|
||||||
const pathParam = window.location.pathname.substring("/claim/".length);
|
|
||||||
let claimId;
|
|
||||||
if (pathParam) {
|
|
||||||
claimId = decodeURIComponent(pathParam);
|
|
||||||
this.loadClaim(claimId, identity);
|
|
||||||
} else {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "No claim ID was provided.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalConfirmers() {
|
|
||||||
return (
|
|
||||||
this.numConfsNotVisible +
|
|
||||||
this.confirmerIdList.length +
|
|
||||||
this.confsVisibleToIdList.length
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getIdentity(activeDid: string): Promise<IIdentifier> {
|
|
||||||
await accountsDB.open();
|
|
||||||
const account = (await accountsDB.accounts
|
|
||||||
.where("did")
|
|
||||||
.equals(activeDid)
|
|
||||||
.first()) as Account;
|
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
|
||||||
|
|
||||||
if (!identity) {
|
|
||||||
throw new Error(
|
|
||||||
"Attempted to load project records with no identity available.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getHeaders(identity: IIdentifier) {
|
|
||||||
const headers: RawAxiosRequestHeaders = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
if (identity) {
|
|
||||||
const token = await accessToken(identity);
|
|
||||||
headers["Authorization"] = "Bearer " + token;
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Isn't there a better way to make this available to the template?
|
|
||||||
didInfo(
|
|
||||||
did: string,
|
|
||||||
activeDid: string,
|
|
||||||
dids: Array<string>,
|
|
||||||
contacts: Array<Contact>,
|
|
||||||
) {
|
|
||||||
return serverUtil.didInfo(did, activeDid, dids, contacts);
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadClaim(claimId: string, identity: IIdentifier) {
|
|
||||||
const url =
|
|
||||||
this.apiServer + "/api/claim/byHandle/" + encodeURIComponent(claimId);
|
|
||||||
const headers = await this.getHeaders(identity);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await this.axios.get(url, { headers });
|
|
||||||
if (resp.status === 200) {
|
|
||||||
this.veriClaim = resp.data;
|
|
||||||
} else {
|
|
||||||
// actually, axios typically throws an error so we never get here
|
|
||||||
console.log("Error getting claim:", resp);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "There was a problem getting that claim. See logs for more info.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const serverError = error as AxiosError;
|
|
||||||
console.error("Error retrieving claim:", serverError);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "Something went wrong retrieving that claim. See logs for more info.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmUrl =
|
|
||||||
this.apiServer +
|
|
||||||
"/api/report/issuersWhoClaimedOrConfirmed?claimId=" +
|
|
||||||
encodeURIComponent(serverUtil.stripEndorserPrefix(claimId));
|
|
||||||
const confirmHeaders = await this.getHeaders(identity);
|
|
||||||
try {
|
|
||||||
const response = await this.axios.get(confirmUrl, {
|
|
||||||
headers: confirmHeaders,
|
|
||||||
});
|
|
||||||
if (response.status === 200) {
|
|
||||||
const resultList1 = response.data.result || [];
|
|
||||||
const resultList2 = R.reject(serverUtil.isHiddenDid, resultList1);
|
|
||||||
const resultList3 = R.reject(
|
|
||||||
(did: string) => did === this.veriClaim.issuer,
|
|
||||||
resultList2,
|
|
||||||
);
|
|
||||||
this.confirmerIdList = resultList3;
|
|
||||||
this.numConfsNotVisible = resultList1.length - resultList2.length;
|
|
||||||
if (resultList3.length === resultList2.length) {
|
|
||||||
// the issuer was not in the "visible" list so they must be hidden
|
|
||||||
// so subtract them from the non-visible confirmers count
|
|
||||||
this.numConfsNotVisible = this.numConfsNotVisible - 1;
|
|
||||||
}
|
|
||||||
this.confsVisibleToIdList =
|
|
||||||
response.data.result.resultVisibleToDids || [];
|
|
||||||
} else {
|
|
||||||
this.confsVisibleErrorMessage =
|
|
||||||
"Had problems retrieving confirmations.";
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const serverError = error as AxiosError;
|
|
||||||
console.error("Error retrieving confirmations:", serverError);
|
|
||||||
this.confsVisibleErrorMessage =
|
|
||||||
"Had problems retrieving confirmations. See logs for more info.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async showFullClaim(claimId: string) {
|
|
||||||
await accountsDB.open();
|
|
||||||
const accounts = accountsDB.accounts;
|
|
||||||
const accountsArr = await accounts?.toArray();
|
|
||||||
const account = accountsArr.find((acc) => acc.did === this.activeDid);
|
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
|
||||||
|
|
||||||
const url =
|
|
||||||
this.apiServer + "/api/claim/full/" + encodeURIComponent(claimId);
|
|
||||||
const headers = await this.getHeaders(identity);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await this.axios.get(url, { headers });
|
|
||||||
if (resp.status === 200) {
|
|
||||||
this.fullClaim = resp.data;
|
|
||||||
} else {
|
|
||||||
// actually, axios typically throws an error so we never get here
|
|
||||||
console.log("Error getting full claim:", resp);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "There was a problem getting that claim. See logs for more info.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error("Error retrieving full claim:", error);
|
|
||||||
const serverError = error as AxiosError;
|
|
||||||
if (serverError.response?.status === 403) {
|
|
||||||
this.fullClaimMessage =
|
|
||||||
"You are not authorized to view the full contents of this claim." +
|
|
||||||
" To see all the details, ask the issuer to allow you to see their claims." +
|
|
||||||
" If you cannot see the issuer's DID, ask someone in the Confirmations section above." +
|
|
||||||
" If there are no connections, you will have to ask people in your" +
|
|
||||||
" network for their help, some other way; send them to this page and" +
|
|
||||||
" see if they can make a connection for you.";
|
|
||||||
} else {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "Something went wrong retrieving that claim. See logs for more info.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async confirmClaim() {
|
|
||||||
if (confirm("Do you personally confirm that this is true?")) {
|
|
||||||
// similar logic is found in endorser-mobile
|
|
||||||
const goodClaim = serverUtil.removeSchemaContext(
|
|
||||||
serverUtil.removeVisibleToDids(
|
|
||||||
serverUtil.addLastClaimOrHandleAsIdIfMissing(
|
|
||||||
this.veriClaim.claim,
|
|
||||||
this.veriClaim.id,
|
|
||||||
this.veriClaim.handleId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const confirmationClaim: serverUtil.GenericVerifiableCredential & {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
object: any;
|
|
||||||
} = {
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "AgreeAction",
|
|
||||||
object: goodClaim,
|
|
||||||
};
|
|
||||||
const result = await serverUtil.createAndSubmitClaim(
|
|
||||||
confirmationClaim,
|
|
||||||
await this.getIdentity(this.activeDid),
|
|
||||||
this.apiServer,
|
|
||||||
this.axios,
|
|
||||||
);
|
|
||||||
if (result.type === "success") {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "success",
|
|
||||||
title: "Success",
|
|
||||||
text: "Confirmation submitted.",
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log("Got error submitting the confirmation:", result);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "There was a problem submitting the confirmation. See logs for more info.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -124,7 +124,7 @@ interface Notification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class ContactAmountssView extends Vue {
|
export default class ContactsView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
|
|||||||
@@ -18,16 +18,6 @@
|
|||||||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4">
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4">
|
||||||
Your Contact Info
|
Your Contact Info
|
||||||
</h1>
|
</h1>
|
||||||
<p v-if="!givenName" class="text-center mt-2">
|
|
||||||
<span class="text-red">Beware!</span>
|
|
||||||
You aren't sharing your name, so hurry and
|
|
||||||
<router-link
|
|
||||||
:to="{ name: 'new-edit-account' }"
|
|
||||||
class="bg-blue-500 text-white px-1.5 py-1 rounded-md"
|
|
||||||
>
|
|
||||||
go here to set it for them.
|
|
||||||
</router-link>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div @click="onCopyToClipboard()" v-if="activeDid">
|
<div @click="onCopyToClipboard()" v-if="activeDid">
|
||||||
@@ -97,7 +87,6 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
givenName = "";
|
|
||||||
qrValue = "";
|
qrValue = "";
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid: string) {
|
||||||
@@ -122,7 +111,6 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.givenName = settings?.firstName || "";
|
|
||||||
|
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
|
|||||||
@@ -19,13 +19,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- New Contact -->
|
<!-- New Contact -->
|
||||||
<div class="mb-4 flex items-stretch">
|
<div class="mb-4 flex">
|
||||||
<router-link
|
<span class="self-center bg-slate-500 text-white px-1.5 py-1 rounded-md">
|
||||||
:to="{ name: 'contact-qr' }"
|
<router-link :to="{ name: 'contact-qr' }">
|
||||||
class="flex items-center bg-slate-500 text-white px-1.5 py-1 mr-1 rounded-md"
|
<fa icon="qrcode" class="fa-fw" />
|
||||||
>
|
</router-link>
|
||||||
<fa icon="qrcode" class="fa-fw text-2xl" />
|
</span>
|
||||||
</router-link>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="DID, Name, Public Key (base 16 or 64)"
|
placeholder="DID, Name, Public Key (base 16 or 64)"
|
||||||
@@ -171,7 +170,7 @@
|
|||||||
<button
|
<button
|
||||||
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-l-md"
|
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-l-md"
|
||||||
@click="onClickAddGive(activeDid, contact.did)"
|
@click="onClickAddGive(activeDid, contact.did)"
|
||||||
:title="givenByMeDescriptions[contact.did] || ''"
|
title="givenByMeDescriptions[contact.did]"
|
||||||
>
|
>
|
||||||
To:
|
To:
|
||||||
{{
|
{{
|
||||||
@@ -190,7 +189,7 @@
|
|||||||
<button
|
<button
|
||||||
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-r-md -ml-1.5 border-l border-blue-400"
|
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-r-md -ml-1.5 border-l border-blue-400"
|
||||||
@click="onClickAddGive(contact.did, activeDid)"
|
@click="onClickAddGive(contact.did, activeDid)"
|
||||||
:title="givenToMeDescriptions[contact.did] || ''"
|
title="givenToMeDescriptions[contact.did]"
|
||||||
>
|
>
|
||||||
From:
|
From:
|
||||||
{{
|
{{
|
||||||
@@ -336,7 +335,7 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string): Promise<IIdentifier> {
|
public async getIdentity(activeDid: string) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
const account = R.find((acc) => acc.did === activeDid, accounts) as Account;
|
const account = R.find((acc) => acc.did === activeDid, accounts) as Account;
|
||||||
@@ -367,10 +366,6 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadGives() {
|
async loadGives() {
|
||||||
if (!this.activeDid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleResponse = (
|
const handleResponse = (
|
||||||
resp: { status: number; data: { data: GiveServerRecord[] } },
|
resp: { status: number; data: { data: GiveServerRecord[] } },
|
||||||
descriptions: Record<string, string>,
|
descriptions: Record<string, string>,
|
||||||
@@ -405,11 +400,11 @@ export default class ContactsView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Retrieval Error",
|
title: "Server Error",
|
||||||
text:
|
text:
|
||||||
"Got an error retrieving your " +
|
"Got an error retrieving your " +
|
||||||
(useRecipient ? "given" : "received") +
|
(useRecipient ? "given" : "received") +
|
||||||
" data from the server.",
|
" time from the server.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -460,13 +455,12 @@ export default class ContactsView extends Vue {
|
|||||||
this.givenToMeConfirmed = givenToMeConfirmed;
|
this.givenToMeConfirmed = givenToMeConfirmed;
|
||||||
this.givenToMeUnconfirmed = givenToMeUnconfirmed;
|
this.givenToMeUnconfirmed = givenToMeUnconfirmed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error loading gives", error);
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Load Error",
|
title: "Server Error",
|
||||||
text: "Got an error loading your gives.",
|
text: error as string,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -710,7 +704,6 @@ export default class ContactsView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error when registering:", error);
|
|
||||||
let userMessage = "There was an error. See logs for more info.";
|
let userMessage = "There was an error. See logs for more info.";
|
||||||
const serverError = error as AxiosError;
|
const serverError = error as AxiosError;
|
||||||
if (serverError) {
|
if (serverError) {
|
||||||
@@ -727,7 +720,7 @@ export default class ContactsView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Registration Error",
|
title: "Server Error",
|
||||||
text: userMessage,
|
text: userMessage,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
@@ -773,28 +766,27 @@ export default class ContactsView extends Vue {
|
|||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
"Got some bad server response when setting visibility: ",
|
"Got some bad server response when setting visibility: ",
|
||||||
resp.status,
|
|
||||||
resp,
|
resp,
|
||||||
);
|
);
|
||||||
const message =
|
const message =
|
||||||
resp.data.error?.message || "Got some error setting visibility.";
|
resp.data.error?.message || "Bad server response of " + resp.status;
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error Setting Visibility",
|
title: "Server Error",
|
||||||
text: message,
|
text: message,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Got some error when setting visibility:", err);
|
console.error("Got some server error when setting visibility:", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error Setting Visibility",
|
title: "Server Error",
|
||||||
text: "Check connectivity and try again.",
|
text: "Check connectivity and try again.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
@@ -837,19 +829,19 @@ export default class ContactsView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error Checking Visibility",
|
title: "Server Error",
|
||||||
text: message,
|
text: message,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Caught error from request to check visibility:", err);
|
console.log("Caught error from server request to check visibility:", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error Checking Visibility",
|
title: "Server Error",
|
||||||
text: "Check connectivity and try again.",
|
text: "Check connectivity and try again.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
@@ -1035,7 +1027,6 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error in createAndSubmitGive: ", error);
|
|
||||||
let userMessage = "There was an error. See logs for more info.";
|
let userMessage = "There was an error. See logs for more info.";
|
||||||
const serverError = error as AxiosError;
|
const serverError = error as AxiosError;
|
||||||
if (serverError) {
|
if (serverError) {
|
||||||
@@ -1052,7 +1043,7 @@ export default class ContactsView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error Sending Give",
|
title: "Server Error",
|
||||||
text: userMessage,
|
text: userMessage,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<QuickNav selected="Discover"></QuickNav>
|
<QuickNav selected="Discover"></QuickNav>
|
||||||
<TopMessage />
|
|
||||||
|
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="p-6 pb-24">
|
<section id="Content" class="p-6 pb-24">
|
||||||
@@ -137,7 +136,6 @@ import { didInfo, ProjectData } from "@/libs/endorserServer";
|
|||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
|
||||||
|
|
||||||
interface Notification {
|
interface Notification {
|
||||||
group: string;
|
group: string;
|
||||||
@@ -151,7 +149,6 @@ interface Notification {
|
|||||||
QuickNav,
|
QuickNav,
|
||||||
InfiniteScroll,
|
InfiniteScroll,
|
||||||
EntityIcon,
|
EntityIcon,
|
||||||
TopMessage,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class DiscoverView extends Vue {
|
export default class DiscoverView extends Vue {
|
||||||
|
|||||||
@@ -28,35 +28,13 @@
|
|||||||
<p>Somehow call the service-worker self.showNotification</p>
|
<p>Somehow call the service-worker self.showNotification</p>
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold">Check OS-level permissions</h2>
|
<h2 class="text-xl font-semibold">Check OS-level permissions</h2>
|
||||||
<div>
|
<p>
|
||||||
Walk-throughs & screenshots, maybe for all combinations of OS &
|
Walk-throughs & screenshots, maybe for all combinations of OS &
|
||||||
browsers.
|
browsers.
|
||||||
<div>
|
</p>
|
||||||
<h3 class="text-lg font-semibold">Mobile Phone - Apple iOS</h3>
|
|
||||||
<div>
|
|
||||||
Notifications require iOS 16.4 or higher. To check your iOS version,
|
|
||||||
go to Settings > General > About > Software Version.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold">Mobile Phone - Google Android</h3>
|
|
||||||
<div>See the browser section.</div>
|
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold">Desktop - Mac</h3>
|
|
||||||
<div>Requires Mac OS 13.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold">Check browser-level permissions</h2>
|
<h2 class="text-xl font-semibold">Check browser-level permissions</h2>
|
||||||
<p>Walk-throughs & screenshots for browser settings</p>
|
<p>Walk-throughs & screenshots for browser settings</p>
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-semibold">Mobile Phone - Apple iOS</h3>
|
|
||||||
<div>Make sure your OS (above) supports it.</div>
|
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold">Mobile Phone - Android</h3>
|
|
||||||
<div>Chrome requires version 50.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold">Explain full reset to start again</h2>
|
<h2 class="text-xl font-semibold">Explain full reset to start again</h2>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -214,7 +214,9 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold">What app version is this?</h2>
|
<h2 class="text-xl font-semibold">What app version is this?</h2>
|
||||||
<p>{{ package.version }} ({{ commitHash }})</p>
|
<p>
|
||||||
|
{{ package.version }}
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold">
|
<h2 class="text-xl font-semibold">
|
||||||
For any other questions, including removing your data:
|
For any other questions, including removing your data:
|
||||||
@@ -244,7 +246,6 @@ export default class Help extends Vue {
|
|||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||||||
|
|
||||||
package = Package;
|
package = Package;
|
||||||
commitHash = process.env.VUE_APP_GIT_HASH;
|
|
||||||
|
|
||||||
showOnboardInfo() {
|
showOnboardInfo() {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<QuickNav selected="Home"></QuickNav>
|
<QuickNav selected="Home"></QuickNav>
|
||||||
<TopMessage />
|
|
||||||
|
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="p-6 pb-24">
|
<section id="Content" class="p-6 pb-24">
|
||||||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
||||||
@@ -10,44 +8,28 @@
|
|||||||
|
|
||||||
<!-- show the actions for recognizing a give -->
|
<!-- show the actions for recognizing a give -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div
|
<div v-if="!activeDid">
|
||||||
v-if="!activeDid"
|
To record others' giving,
|
||||||
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
|
<router-link :to="{ name: 'start' }" class="text-blue-500">
|
||||||
>
|
create your identifier.</router-link
|
||||||
<p class="text-lg mb-3">
|
|
||||||
You need an <b>identifier</b> before you can record anyone's gives.
|
|
||||||
</p>
|
|
||||||
<router-link
|
|
||||||
:to="{ name: 'start' }"
|
|
||||||
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
|
|
||||||
>
|
|
||||||
Create Your Identifier</router-link
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div v-else-if="!isRegistered">
|
||||||
v-else-if="!isRegistered"
|
To record others' giving, someone must register your account, so show
|
||||||
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
|
them
|
||||||
>
|
<router-link :to="{ name: 'contact-qr' }" class="text-blue-500">
|
||||||
Someone must register your account before you can record anyone's gives.
|
your identity info</router-link
|
||||||
To do this:
|
|
||||||
<router-link
|
|
||||||
:to="{ name: 'contact-qr' }"
|
|
||||||
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
|
|
||||||
>
|
>
|
||||||
1. Show Them Your Identity Info</router-link
|
and then
|
||||||
>
|
<router-link :to="{ name: 'account' }" class="text-blue-500">
|
||||||
<router-link
|
check your limits.</router-link
|
||||||
:to="{ name: 'account' }"
|
|
||||||
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
|
|
||||||
>
|
|
||||||
2. Check Your Limits</router-link
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<!-- activeDid && isRegistered -->
|
<!-- activeDid && isRegistered -->
|
||||||
<h2 class="text-xl font-bold mb-4">Record Something Given</h2>
|
<h2 class="text-xl font-bold">Record Something Given</h2>
|
||||||
|
|
||||||
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
||||||
<li @click="openDialog()">
|
<li @click="openDialog()">
|
||||||
@@ -105,53 +87,40 @@
|
|||||||
showGivenToUser="true"
|
showGivenToUser="true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 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>
|
<h2 class="text-xl font-bold mb-4">Latest Activity</h2>
|
||||||
<InfiniteScroll @reached-bottom="loadMoreGives">
|
|
||||||
<ul class="border-t border-slate-300">
|
|
||||||
<li
|
|
||||||
class="border-b border-slate-300 py-2"
|
|
||||||
v-for="record in feedData"
|
|
||||||
:key="record.jwtId"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm"
|
|
||||||
v-if="record.jwtId == feedLastViewedClaimId"
|
|
||||||
>
|
|
||||||
You've seen all the following
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex">
|
|
||||||
<fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa>
|
|
||||||
<span class="">{{ this.giveDescription(record) }}</span>
|
|
||||||
<a @click="onClickLoadClaim(record.jwtId)">
|
|
||||||
<fa icon="circle-info" class="pl-2 pt-1 text-slate-500"></fa>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</InfiniteScroll>
|
|
||||||
<div :class="{ hidden: isHiddenSpinner }">
|
<div :class="{ hidden: isHiddenSpinner }">
|
||||||
<p class="text-slate-500 text-center italic mt-4 mb-4">
|
<p class="text-slate-500 text-center italic mt-4 mb-4">
|
||||||
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading…
|
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading…
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<ul class="border-t border-slate-300">
|
||||||
|
<li
|
||||||
|
class="border-b border-slate-300 py-2"
|
||||||
|
v-for="record in feedData"
|
||||||
|
:key="record.jwtId"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm"
|
||||||
|
v-if="record.jwtId == feedLastViewedId"
|
||||||
|
>
|
||||||
|
You've seen all the following
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa>
|
||||||
|
<!-- icon values: "coins" = money; "clock" = time; "gift" = others -->
|
||||||
|
<span class="">{{ this.giveDescription(record) }}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db/index";
|
||||||
import { Account } from "@/db/tables/accounts";
|
|
||||||
import { Contact } from "@/db/tables/contacts";
|
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import {
|
import {
|
||||||
@@ -159,7 +128,11 @@ import {
|
|||||||
GiverInputInfo,
|
GiverInputInfo,
|
||||||
GiveServerRecord,
|
GiveServerRecord,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
|
import { Account } from "@/db/tables/accounts";
|
||||||
|
|
||||||
interface Notification {
|
interface Notification {
|
||||||
group: string;
|
group: string;
|
||||||
@@ -169,13 +142,7 @@ interface Notification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: { GiftedDialog, QuickNav, EntityIcon },
|
||||||
GiftedDialog,
|
|
||||||
QuickNav,
|
|
||||||
EntityIcon,
|
|
||||||
InfiniteScroll,
|
|
||||||
TopMessage,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
export default class HomeView extends Vue {
|
export default class HomeView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||||||
@@ -184,9 +151,10 @@ export default class HomeView extends Vue {
|
|||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
|
feedAllLoaded = false;
|
||||||
feedData = [];
|
feedData = [];
|
||||||
feedPreviousOldestId?: string;
|
feedPreviousOldestId?: string;
|
||||||
feedLastViewedClaimId?: string;
|
feedLastViewedId?: string;
|
||||||
isHiddenSpinner = true;
|
isHiddenSpinner = true;
|
||||||
isRegistered = false;
|
isRegistered = false;
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
@@ -226,12 +194,9 @@ export default class HomeView extends Vue {
|
|||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
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.feedLastViewedId = settings?.lastViewedClaimId;
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
|
||||||
// this returns a Promise but we don't need to wait for it
|
|
||||||
this.updateAllFeed();
|
this.updateAllFeed();
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -274,33 +239,24 @@ export default class HomeView extends Vue {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Data loader used by infinite scroller
|
|
||||||
* @param payload is the flag from the InfiniteScroll indicating if it should load
|
|
||||||
**/
|
|
||||||
public async loadMoreGives(payload: boolean) {
|
|
||||||
if (payload) {
|
|
||||||
this.updateAllFeed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateAllFeed() {
|
public async updateAllFeed() {
|
||||||
this.isHiddenSpinner = false;
|
this.isHiddenSpinner = false;
|
||||||
await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
|
await this.retrieveClaims(this.apiServer, this.feedPreviousOldestId)
|
||||||
.then(async (results) => {
|
.then(async (results) => {
|
||||||
if (results.data.length > 0) {
|
if (results.data.length > 0) {
|
||||||
this.feedData = this.feedData.concat(results.data);
|
this.feedData = this.feedData.concat(results.data);
|
||||||
|
this.feedAllLoaded = results.hitLimit;
|
||||||
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.
|
|
||||||
if (
|
if (
|
||||||
this.feedLastViewedClaimId == null ||
|
this.feedLastViewedId == null ||
|
||||||
this.feedLastViewedClaimId < results.data[0].jwtId
|
this.feedLastViewedId < results.data[0].jwtId
|
||||||
) {
|
) {
|
||||||
await db.open();
|
await db.open();
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
lastViewedClaimId: results.data[0].jwtId,
|
lastViewedClaimId: results.data[0].jwtId,
|
||||||
});
|
});
|
||||||
|
// but not for this page because we need to remember what it was before
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -310,22 +266,17 @@ export default class HomeView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Feed Error",
|
title: "Export Error",
|
||||||
text: e.userMessage || "There was an error retrieving feed data.",
|
text: e.userMessage || "There was an error retrieving feed data.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isHiddenSpinner = true;
|
this.isHiddenSpinner = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public async retrieveClaims(endorserApiServer: string, beforeId?: string) {
|
||||||
* Retrieve claims in reverse chronological order
|
|
||||||
*
|
|
||||||
* @param beforeId the earliest ID (of previous searches) to search earlier
|
|
||||||
* @return claims in reverse chronological order
|
|
||||||
*/
|
|
||||||
public async retrieveGives(endorserApiServer: string, beforeId?: string) {
|
|
||||||
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
|
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
endorserApiServer + "/api/v2/report/gives?" + beforeQuery,
|
endorserApiServer + "/api/v2/report/gives?" + beforeQuery,
|
||||||
@@ -390,13 +341,6 @@ export default class HomeView extends Vue {
|
|||||||
return giverInfo + " gave" + gaveRecipientInfo + ": " + gaveAmount;
|
return giverInfo + " gave" + gaveRecipientInfo + ": " + gaveAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickLoadClaim(jwtId: string) {
|
|
||||||
const route = {
|
|
||||||
path: "/claim/" + encodeURIComponent(jwtId),
|
|
||||||
};
|
|
||||||
this.$router.push(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
displayAmount(code: string, amt: number) {
|
displayAmount(code: string, amt: number) {
|
||||||
return "" + amt + " " + this.currencyShortWordForCode(code, amt === 1);
|
return "" + amt + " " + this.currencyShortWordForCode(code, amt === 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export default class NewEditAccountView extends Vue {
|
|||||||
});
|
});
|
||||||
localStorage.setItem("firstName", this.givenName as string);
|
localStorage.setItem("firstName", this.givenName as string);
|
||||||
localStorage.setItem("lastName", ""); // deprecated, pre v 0.1.3
|
localStorage.setItem("lastName", ""); // deprecated, pre v 0.1.3
|
||||||
this.$router.back();
|
this.$router.push({ name: "account" });
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickCancel() {
|
onClickCancel() {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
maxlength="5000"
|
maxlength="5000"
|
||||||
></textarea>
|
></textarea>
|
||||||
<div class="text-xs text-slate-500 italic -mt-3 mb-4">
|
<div class="text-xs text-slate-500 italic -mt-3 mb-4">
|
||||||
{{ fullClaim.description?.length }}/5000 max. characters
|
{{ fullClaim.description.length }}/5000 max. characters
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export default class NewIdentifierView extends Vue {
|
|||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.$router.push({ name: "home" });
|
this.$router.push({ name: "account" });
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<QuickNav />
|
<QuickNav selected="Projects"></QuickNav>
|
||||||
<TopMessage />
|
|
||||||
|
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="p-6 pb-24">
|
<section id="Content" class="p-6 pb-24">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
@@ -90,8 +88,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="activeDid" class="mb-4">
|
<div class="mb-4">
|
||||||
<div class="text-center">
|
<div v-if="activeDid" class="text-center">
|
||||||
<button
|
<button
|
||||||
@click="openOfferDialog({ name: 'you', did: activeDid })"
|
@click="openOfferDialog({ name: 'you', did: activeDid })"
|
||||||
class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
|
class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
|
||||||
@@ -101,16 +99,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="activeDid">
|
<div>
|
||||||
<div class="text-center">
|
<div v-if="activeDid" class="text-center">
|
||||||
<button
|
<button
|
||||||
@click="openGiftDialog({ name: 'you', did: activeDid })"
|
@click="openGiftDialog({ name: 'you', did: activeDid })"
|
||||||
class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
|
class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
|
||||||
>
|
>
|
||||||
I gave…
|
I gave…
|
||||||
</button>
|
</button>
|
||||||
<p class="mt-2 mb-4 text-center">Or, record a contribution from:</p>
|
<p class="mt-2 mb-4 text-center">Or, record a gift from:</p>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="!activeDid" class="mt-2 mb-4">Record a gift from:</p>
|
||||||
|
|
||||||
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
||||||
<li @click="openGiftDialog()">
|
<li @click="openGiftDialog()">
|
||||||
@@ -175,9 +174,6 @@
|
|||||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||||
{{ didInfo(offer.agentDid, activeDid, allMyDids, allContacts) }}
|
{{ didInfo(offer.agentDid, activeDid, allMyDids, allContacts) }}
|
||||||
</span>
|
</span>
|
||||||
<a @click="onClickLoadClaim(offer.jwtId)">
|
|
||||||
<fa icon="circle-info" class="pl-2 pt-1 text-slate-500"></fa>
|
|
||||||
</a>
|
|
||||||
<span v-if="offer.amount">
|
<span v-if="offer.amount">
|
||||||
<fa
|
<fa
|
||||||
:icon="iconForUnitCode(offer.unit)"
|
:icon="iconForUnitCode(offer.unit)"
|
||||||
@@ -209,9 +205,6 @@
|
|||||||
><fa icon="user" class="fa-fw text-slate-400"></fa>
|
><fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||||
{{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }}
|
{{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }}
|
||||||
</span>
|
</span>
|
||||||
<a @click="onClickLoadClaim(give.jwtId)">
|
|
||||||
<fa icon="circle-info" class="pl-2 pt-1 text-slate-500"></fa>
|
|
||||||
</a>
|
|
||||||
<span v-if="give.amount">
|
<span v-if="give.amount">
|
||||||
<fa
|
<fa
|
||||||
:icon="iconForUnitCode(give.unit)"
|
:icon="iconForUnitCode(give.unit)"
|
||||||
@@ -235,32 +228,28 @@
|
|||||||
<h3 class="text-sm uppercase font-semibold mb-3">
|
<h3 class="text-sm uppercase font-semibold mb-3">
|
||||||
Contributions To This Idea
|
Contributions To This Idea
|
||||||
</h3>
|
</h3>
|
||||||
<!-- centering because long, wrapped project names didn't left align with blank or "text-left" -->
|
<ul>
|
||||||
<div class="text-center">
|
<li v-for="plan in fulfillersToThis" :key="plan.handleId">
|
||||||
<div v-for="plan in fulfillersToThis" :key="plan.handleId">
|
|
||||||
<button
|
<button
|
||||||
@click="onClickLoadProject(plan.handleId)"
|
@click="onClickLoadProject(plan.handleId)"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
{{ plan.name }}
|
{{ plan.name }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
|
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
|
||||||
<h3 class="text-sm uppercase font-semibold mb-3">
|
<h3 class="text-sm uppercase font-semibold mb-3">
|
||||||
Contributions By This Idea
|
Contributions By This Idea
|
||||||
</h3>
|
</h3>
|
||||||
<!-- centering because long, wrapped project names didn't left align with blank or "text-left" -->
|
<button
|
||||||
<div class="text-center">
|
@click="onClickLoadProject(fulfilledByThis.handleId)"
|
||||||
<button
|
class="text-blue-500"
|
||||||
@click="onClickLoadProject(fulfilledByThis.handleId)"
|
>
|
||||||
class="text-blue-500"
|
{{ fulfilledByThis.name }}
|
||||||
>
|
</button>
|
||||||
{{ fulfilledByThis.name }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -284,7 +273,6 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
|
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import OfferDialog from "@/components/OfferDialog.vue";
|
import OfferDialog from "@/components/OfferDialog.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
@@ -309,7 +297,7 @@ interface Notification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav, TopMessage },
|
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav },
|
||||||
})
|
})
|
||||||
export default class ProjectViewView extends Vue {
|
export default class ProjectViewView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||||||
@@ -362,6 +350,12 @@ export default class ProjectViewView extends Vue {
|
|||||||
.equals(activeDid)
|
.equals(activeDid)
|
||||||
.first()) as Account;
|
.first()) as Account;
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
const identity = JSON.parse(account?.identity || "null");
|
||||||
|
|
||||||
|
if (!identity) {
|
||||||
|
throw new Error(
|
||||||
|
"Attempted to load project records with no identity available.",
|
||||||
|
);
|
||||||
|
}
|
||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,21 +423,19 @@ export default class ProjectViewView extends Vue {
|
|||||||
this.latitude = resp.data.claim?.location?.geo?.latitude || 0;
|
this.latitude = resp.data.claim?.location?.geo?.latitude || 0;
|
||||||
this.longitude = resp.data.claim?.location?.geo?.longitude || 0;
|
this.longitude = resp.data.claim?.location?.geo?.longitude || 0;
|
||||||
this.url = resp.data.claim?.url || "";
|
this.url = resp.data.claim?.url || "";
|
||||||
} else {
|
} else if (resp.status === 404) {
|
||||||
// actually, axios throws an error on 404 so we probably never get here
|
// actually, axios throws an error so we never get here
|
||||||
console.log("Error getting project:", resp);
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: "There was a problem getting that project. See logs for more info.",
|
text: "That project does not exist.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.error("Error retrieving project:", error);
|
|
||||||
const serverError = error as AxiosError;
|
const serverError = error as AxiosError;
|
||||||
if (serverError.response?.status === 404) {
|
if (serverError.response?.status === 404) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -465,6 +457,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
|
console.error("Error retrieving project:", serverError.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -648,13 +641,6 @@ export default class ProjectViewView extends Vue {
|
|||||||
(this.$refs.customOfferDialog as OfferDialog).open();
|
(this.$refs.customOfferDialog as OfferDialog).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickLoadClaim(jwtId: string) {
|
|
||||||
const route = {
|
|
||||||
path: "/claim/" + encodeURIComponent(jwtId),
|
|
||||||
};
|
|
||||||
this.$router.push(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_CODES: Record<string, Record<string, string>> = {
|
UNIT_CODES: Record<string, Record<string, string>> = {
|
||||||
BTC: {
|
BTC: {
|
||||||
name: "Bitcoin",
|
name: "Bitcoin",
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<QuickNav selected="Projects"></QuickNav>
|
<QuickNav selected="Projects"></QuickNav>
|
||||||
<TopMessage />
|
|
||||||
|
|
||||||
<section id="Content" class="p-6 pb-24">
|
<section id="Content" class="p-6 pb-24">
|
||||||
<!-- Heading -->
|
<!-- Heading -->
|
||||||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
||||||
Your Ideas
|
Your Plans
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<!-- Quick Search -->
|
<!-- Quick Search -->
|
||||||
@@ -81,7 +79,6 @@ import { IIdentifier } from "@veramo/core";
|
|||||||
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 EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
|
||||||
import { ProjectData } from "@/libs/endorserServer";
|
import { ProjectData } from "@/libs/endorserServer";
|
||||||
|
|
||||||
interface Notification {
|
interface Notification {
|
||||||
@@ -92,7 +89,7 @@ interface Notification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { InfiniteScroll, QuickNav, EntityIcon, TopMessage },
|
components: { InfiniteScroll, QuickNav, EntityIcon },
|
||||||
})
|
})
|
||||||
export default class ProjectsView extends Vue {
|
export default class ProjectsView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ importScripts(
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.addEventListener("install", (event) => {
|
self.addEventListener("install", (event) => {
|
||||||
console.log("Adding event listener for:", event);
|
console.error("Adding event listener for:", event);
|
||||||
importScripts(
|
importScripts(
|
||||||
"safari-notifications.js",
|
"safari-notifications.js",
|
||||||
"nacl.js",
|
"nacl.js",
|
||||||
|
|||||||
@@ -395,12 +395,12 @@ async function setMostRecentNotified(id) {
|
|||||||
data["lastNotifiedClaimId"] = id;
|
data["lastNotifiedClaimId"] = id;
|
||||||
await updateRecord(store, data);
|
await updateRecord(store, data);
|
||||||
} else {
|
} else {
|
||||||
console.error("IndexedDB settings record not found.");
|
console.error("Record not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.oncomplete = () => db.close();
|
transaction.oncomplete = () => db.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("IndexedDB error:", error);
|
console.error("Database error: " + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,11 +520,7 @@ async function getNotificationCount() {
|
|||||||
const most_recent_notified = claims[0]["id"];
|
const most_recent_notified = claims[0]["id"];
|
||||||
await setMostRecentNotified(most_recent_notified);
|
await setMostRecentNotified(most_recent_notified);
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error("The service worker got a bad response status when fetching claims:", response.status, response);
|
||||||
"The service worker got a bad response status when fetching claims:",
|
|
||||||
response.status,
|
|
||||||
response,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
const { defineConfig } = require("@vue/cli-service");
|
const { defineConfig } = require("@vue/cli-service");
|
||||||
const { gitDescribeSync } = require("git-describe");
|
|
||||||
|
|
||||||
process.env.VUE_APP_GIT_HASH = gitDescribeSync().hash;
|
|
||||||
|
|
||||||
module.exports = defineConfig({
|
module.exports = defineConfig({
|
||||||
transpileDependencies: true,
|
transpileDependencies: true,
|
||||||
configureWebpack: {
|
configureWebpack: {
|
||||||
|
|||||||
19
web-push.md
19
web-push.md
@@ -400,22 +400,3 @@ While notifications are turned on, the user can tap on the App Notifications tog
|
|||||||
* Active. (User can change to Muted when the user mutes notifications.)
|
* Active. (User can change to Muted when the user mutes notifications.)
|
||||||
* Muted. (User can change to Active when the user toggles it.)
|
* Muted. (User can change to Active when the user toggles it.)
|
||||||
(Turning mute off automatically after some amount of time is not planned in version 1.)
|
(Turning mute off automatically after some amount of time is not planned in version 1.)
|
||||||
|
|
||||||
|
|
||||||
# TROUBLESHOOTING
|
|
||||||
|
|
||||||
## Desktop
|
|
||||||
|
|
||||||
#### Firefox
|
|
||||||
|
|
||||||
Go to `about:debugging` and click on `Inspect` for the service worker.
|
|
||||||
|
|
||||||
#### Chrome
|
|
||||||
|
|
||||||
Go to `chrome://inspect/#service-workers` and click on `Inspect` for the service worker.
|
|
||||||
|
|
||||||
## Mobile
|
|
||||||
|
|
||||||
#### Android
|
|
||||||
|
|
||||||
#### iOS
|
|
||||||
|
|||||||
Reference in New Issue
Block a user