Compare commits

...

70 Commits

Author SHA1 Message Date
333ac773f6 Merge pull request 'A couple small fixes, plus a merge from master' (#1) from trentlarson/crowd-funder-from-jason:feat/vitejs-trent into feat/vitejs
Reviewed-on: #1
2024-04-07 13:52:43 -04:00
1459719a47 remove a lingering debug console.log 2024-04-07 11:39:13 -06:00
5994365a6c fix title of the test app 2024-04-07 11:32:53 -06:00
28f72640d7 add linting before any build 2024-04-07 11:22:20 -06:00
ab81648aca fix linting 2024-04-07 11:02:54 -06:00
5828a290c7 Merge remote-tracking branch 'original-origin/master' into feat/vitejs-trent 2024-04-07 09:41:14 -06:00
78fab735e6 avoid a huge error message in a likely-well-known scenario 2024-04-07 09:24:55 -06:00
2ae165d56f reorder home page vapid check to avoid an error on localhost 2024-04-07 09:16:42 -06:00
0fbd1ad51a add missing Dexie import (which causes failure upon download click) 2024-04-07 09:13:32 -06:00
d49bf61524 on home page, change the filtered button color 2024-04-06 17:58:10 -06:00
1a80bbb714 Merge pull request 'ui-additions-2024-03' (#113) from ui-additions-2024-03 into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#113
2024-04-06 19:46:16 -04:00
5a2a8659f7 Merge branch 'master' into ui-additions-2024-03 2024-04-06 17:45:32 -06:00
0632fb9b39 show in description when recipient is a project (not just Anonymous) 2024-04-06 17:39:40 -06:00
640d273646 filter by selections (now all working), add cache for plans 2024-04-06 14:01:18 -06:00
8f4289c14d Merge pull request 'send a time for notifications to the push server' (#112) from notify-time into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#112
2024-04-03 22:04:36 -04:00
7f56c90d97 Merge pull request 'ui-fixes-2024-03' (#111) from ui-fixes-2024-03 into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#111
2024-04-03 22:04:02 -04:00
8e1daf7015 feed filter: save the changed values to the DB, go to map if no location chosen, reload if necessary 2024-04-03 19:54:01 -06:00
Jose Olarte III
e90a0be6d9 Names and variables for filter toggles 2024-04-03 15:51:23 +08:00
489bb76a60 adjust more code to the PushSubscriptionJSON 2024-04-02 19:32:41 -06:00
2ca33bb9eb adjust the notification-subscription objects to try and send correct info 2024-04-02 19:18:31 -06:00
121181c6a1 add adjustment to UTC hour for notification time 2024-04-02 18:38:44 -06:00
e4cf79b558 update tasks 2024-04-01 19:06:18 -06:00
7692cc2b35 add logic to send a time for notifications 2024-04-01 19:04:54 -06:00
Jose Olarte III
0b4f2484f7 Additions to Account View 2024-03-29 21:41:14 +08:00
Jose Olarte III
cfd53bc186 Removed one more 2024-03-29 15:55:16 +08:00
Jose Olarte III
7f66addfe3 Filter options reduced for release 2024-03-29 15:53:46 +08:00
Jose Olarte III
4635c1ac48 Feed filters dialog 2024-03-27 19:57:31 +08:00
Jose Olarte III
55da3d0b1c Map fix #2 2024-03-26 21:38:21 +08:00
Jose Olarte III
dce4d3cc72 Button width changes
For buttons that are next to each other
2024-03-26 19:55:16 +08:00
Jose Olarte III
e028197a2a Optimized grid space for wider screens 2024-03-26 17:12:55 +08:00
Jose Olarte III
0dab475d8b Fixed map z-index 2024-03-26 16:54:43 +08:00
Jose Olarte III
4e227fc07a Added close icon to gifted prompts dialog 2024-03-26 16:02:24 +08:00
2dfc8fedaa refactor tasks 2024-03-25 19:03:01 -06:00
035f2a5b04 docs: adding do for updated development server run command
- `npm run dev`
2024-03-25 08:15:04 -06:00
09dccc34d6 fix: buffer typescript error in util.ts when parsing ArrayBuffer 2024-03-25 08:10:38 -06:00
b28104af5b bump version and add -beta 2024-03-24 19:04:24 -06:00
3a07e31d63 bump to version 0.3.6 2024-03-24 18:28:42 -06:00
35455e6648 fix check for more camera-device options 2024-03-24 18:27:06 -06:00
0e2c5af16e add onboarding help instructions as separate page 2024-03-24 17:01:53 -06:00
1fc5b0ea2b Merge pull request 'add button during photo to switch to mirror mode' (#109) from photo-reverse into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#109
2024-03-24 18:59:50 -04:00
ca240ab795 fix: es modules syntax for buffer deps instead of commonjs require 2024-03-24 13:05:22 -06:00
01b5ca6ec8 chore: update vitejs config to deploy on the same default port as the @vue/cli-service
This port is 8080. This is done to not break existing tooling and devops code.
2024-03-24 12:05:09 -06:00
6f49260c1e fix: AccountViewView.vue template not resolving dep for dexie-export-import/dist/import
Previous error:

Error: The following dependencies are imported but could not be resolved:

  dexie-export-import/dist/import (imported by /Users/jason/dev/src/trent/crowd-funder-for-time-pwa/src/views/AccountViewView.vue?id=0)

Are they installed?
    at file:///Users/jason/dev/src/trent/crowd-funder-for-time-pwa/node_modules/vite/dist/node/chunks/dep-DJaaTb_D.js:52506:23
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async file:///Users/jason/dev/src/trent/crowd-funder-for-time-pwa/node_modules/vite/dist/node/chunks/dep-DJaaTb_D.js:51972:38
2024-03-24 11:51:58 -06:00
38f44771e9 Initial stab at vitejs update 2024-03-24 11:18:29 -06:00
be2154f847 change icon for detail view (from circle-info to file-lines) 2024-03-23 19:07:53 -06:00
4ad4cab25e add blurb explaining what data is shared with the world 2024-03-23 18:45:26 -06:00
e020caaa50 show warnings before dismissing prompt, and add to tasks and help 2024-03-23 17:35:58 -06:00
40d12b1f9c add button on photo to switch to mirror mode 2024-03-23 16:31:23 -06:00
28754bdfb1 bump version to 0.3.5 2024-03-23 02:41:25 -06:00
2b8f9579f1 fix so that project agent & location removals get saved 2024-03-23 02:31:44 -06:00
6dc0c2cd58 add a camera-switch button 2024-03-23 01:32:55 -06:00
cc6d0958dc Merge pull request 'fix: npm audit fix to resolve vulnerabilities 1 low, 3 moderate, 1 high' (#108) from jsnbuchanan/crowd-funder-for-time-pwa:master into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#108
2024-03-22 12:01:21 -04:00
7ce00b86e8 deps: npm audit fix to resolve vulnerabilities 1 low, 3 moderate, 1 high
There are still 9 moderate severity vulnerabilities, but I will work on those independentally because they may involve updating to library version that have breaking changes.
2024-03-22 09:29:42 -06:00
5e771e4a24 refactor tasks 2024-03-22 07:12:24 -06:00
4dd2c044d5 bump to v 0.3.4 2024-03-21 06:56:35 -06:00
3bfd54362e Merge pull request 'Sample button visual enhancement' (#104) from button-visual-enhancement into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#104
2024-03-21 07:57:18 -04:00
Jose Olarte III
b6e344a15e Propagated button improvements across views 2024-03-21 19:30:42 +08:00
Jose Olarte III
3d1c46aef8 Merge branch 'master' into button-visual-enhancement 2024-03-21 15:40:54 +08:00
ce05f7d003 Merge pull request 'tweak imagery so that it doesn't get stretched on a mobile device' (#107) from photo-ratio into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#107
2024-03-20 20:26:35 -04:00
313cd79e60 finalize the photo-taking code, adding comments and removing logging 2024-03-20 18:23:57 -06:00
121991b53a Merge branch 'gifted-camera-improvements' into photo-ratio 2024-03-20 16:54:39 -06:00
Jose Olarte III
cbf8cb9f46 Fixed placement of upload/retry buttons 2024-03-20 21:11:01 +08:00
Jose Olarte III
fe0668e4b3 Improved Camera Popup 2024-03-20 19:17:18 +08:00
a230506d96 change the X and picture button so that landscape is all functional (if not great-looking) 2024-03-19 21:02:57 -06:00
c49c55d394 change the photo ratios to fix all but portrait-orientation on mobile-emulation 2024-03-18 22:20:02 -06:00
ae572afff6 add help for when service workers get stuck; bump to version 0.3.2 2024-03-17 20:20:13 -06:00
ccea2486e4 change build for test servers, bump version to 0.3.1 2024-03-17 16:42:49 -06:00
155343a9d7 bump to v 0.3.0 2024-03-17 10:49:28 -06:00
85ad295eb9 Merge pull request 'photo-upload' (#105) from photo-upload into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#105
2024-03-17 12:23:32 -04:00
Jose Olarte III
a5c3600673 Sample button visual enhancement 2024-03-07 18:57:08 +08:00
56 changed files with 6983 additions and 14625 deletions

View File

@@ -1,6 +1,7 @@
# Only the variables that start with VUE_APP_ are seen in the application process.env in Vue.
# this won't resolve as a URL on production; it's a URN only found in the test system
VUE_APP_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HNTZYJJXTGT0EZS3VEJGX7AK
VUE_APP_DEFAULT_ENDORSER_API_SERVER=http://localhost:3000
VUE_APP_DEFAULT_IMAGE_API_SERVER=http://localhost:3002
# I tried setting values here and using `vue-cli-service build --mode development`
# but it didn't create some things in "dist":
# - the "css" directory with the CSS extracted from Vue files
# - the sw_scripts-combined* files
#
# ¯\_(ツ)_/¯

View File

@@ -1,4 +1,4 @@
# Only the variables that start with VUE_APP_ are seen in the application process.env in Vue.
VUE_APP_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01GXYPFF7FA03NXKPYY142PY4H
VUE_APP_DEFAULT_ENDORSER_API_SERVER=https://api.endorser.ch
VUE_APP_DEFAULT_IMAGE_API_SERVER=https://image-api.timesafari.app
# Only the variables that start with VITE_ are seen in the application process.env in Vue.
VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01GXYPFF7FA03NXKPYY142PY4H
VITE_DEFAULT_ENDORSER_API_SERVER=https://api.endorser.ch
VITE_DEFAULT_IMAGE_API_SERVER=https://image-api.timesafari.app

View File

@@ -2,6 +2,7 @@ module.exports = {
root: true,
env: {
node: true,
es2022: true,
},
extends: [
"plugin:vue/vue3-essential",
@@ -9,9 +10,9 @@ module.exports = {
"@vue/typescript/recommended",
"plugin:prettier/recommended",
],
parserOptions: {
ecmaVersion: 2020,
},
// parserOptions: {
// ecmaVersion: 2020,
// },
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",

View File

@@ -6,13 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed in DB or environment
- Nothing
## [0.3.6] - 2024.03.24 - 3a07e31d6313ab95711265562d9023c42916e141
### Added
- Button to mirror photo during video
- More detailed onboarding help screen
- Public-data blurb
### Changed in DB or environment
- Nothing
## [0.3.5] - 2024.03.23 - 28754bdfb1e11aa221dd49a5dce4219b69cf6a9d
### Added
- Photo on gift records
### Fixed
- Environment variable for BVC meetings project
- Environment variables and build enhancements for test vs prod
### Changed in DB or environment
- Test that a new browser session will get the right default API
- Test that a new browser session will send the right BVC meetings project
- New environment variable for image API server
- Test that a new browser session will get the right default APIs.
- Test that a new browser session will send the right BVC meetings project.
## [0.2.17] - 2024.03.01 - 3612ea42240c5e1b7d7eff29a39ff18f1b869b36

View File

@@ -18,6 +18,11 @@ npm install
### Compiles and hot-reloads for development
```
npm run dev
```
### Builds the production app
```
npm run serve
```
@@ -34,16 +39,27 @@ npm run lint
* Update the project.task.yaml & CHANGELOG.md & the version in package.json, run `npm install`.
* If production: change package.json to remove "_Test". Also record what version is on production.
* Record what version is currently on production.
* `npm run build-dev` for test servers or `npm run build` for production.
* Run the correct build
* Get on the server and back up the time-safari folder.
* Test
```
# (See .env.development for more details.)
# The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.
VITE_TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HNTZYJJXTGT0EZS3VEJGX7AK VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app npm run build
```
* Production
```
# This picks up values from .env.production
npm run build
```
* Get on the server and back up 3 DBs and the time-safari folder.
* `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari`
* Revert src/constants/app.ts and package.json (if that was prod).
* Commit changes. Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
* [Tag with the new version.](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases)
@@ -115,7 +131,7 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib
* 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", and also manually remove the IndexedDB data if the DBs still show.)
* Clear notification permission. (In Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search for "notifications".)
* 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`.)
* Clear Cache Storage manually, possibly deleting the DB. (In Chrome, in dev tools under Application; in Firefox, in dev tools under Storage.)
(If you find more, add them to the HelpNotificationsView.vue file.)

View File

@@ -1,3 +0,0 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};

17
index.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/favicon.ico">
<title>TimeSafari</title>
</head>
<body>
<noscript>
<strong>We're sorry but TimeSafari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

19091
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,96 +1,91 @@
{
"name": "TimeSafari_Test",
"version": "0.2.18-beta",
"name": "TimeSafari",
"version": "0.3.7-beta",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build-dev": "vue-cli-service build --mode development",
"lint": "vue-cli-service lint"
"dev": "vite",
"serve": "vite preview",
"build": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && vite build",
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src"
},
"dependencies": {
"@dicebear/collection": "^5.3.5",
"@dicebear/core": "^5.3.5",
"@dicebear/collection": "^5.4.1",
"@dicebear/core": "^5.4.1",
"@ethersproject/hdnode": "^5.7.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6",
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
"@tweenjs/tween.js": "^21.0.0",
"@tweenjs/tween.js": "^21.1.1",
"@types/js-yaml": "^4.0.9",
"@types/luxon": "^3.4.2",
"@veramo/core": "^5.4.1",
"@veramo/credential-w3c": "^5.4.1",
"@veramo/data-store": "^5.4.1",
"@veramo/did-manager": "^5.4.1",
"@veramo/did-provider-ethr": "^5.4.1",
"@veramo/did-resolver": "^5.4.1",
"@veramo/key-manager": "^5.4.1",
"@vueuse/core": "^10.4.1",
"@veramo/core": "^5.6.0",
"@veramo/credential-w3c": "^5.6.0",
"@veramo/data-store": "^5.6.0",
"@veramo/did-manager": "^5.6.0",
"@veramo/did-provider-ethr": "^5.6.0",
"@veramo/did-resolver": "^5.6.0",
"@veramo/key-manager": "^5.6.0",
"@vueuse/core": "^10.9.0",
"@zxing/text-encoding": "^0.9.0",
"axios": "^1.5.0",
"buffer": "^6.0.3",
"axios": "^1.6.8",
"class-transformer": "^0.5.1",
"core-js": "^3.32.1",
"dexie": "^3.2.4",
"dexie-export-import": "^4.0.7",
"did-jwt": "^7.2.7",
"ethereum-cryptography": "^2.1.2",
"dexie": "^3.2.7",
"dexie-export-import": "^4.1.1",
"did-jwt": "^7.4.7",
"ethereum-cryptography": "^2.1.3",
"ethereumjs-util": "^7.1.5",
"ethr-did-resolver": "^8.1.2",
"git-describe": "^4.1.1",
"jdenticon": "^3.2.0",
"js-generate-password": "^0.1.9",
"js-yaml": "^4.1.0",
"localstorage-slim": "^2.5.0",
"localstorage-slim": "^2.7.0",
"lru-cache": "^10.2.0",
"luxon": "^3.4.4",
"merkletreejs": "^0.3.11",
"moment": "^2.29.4",
"moment": "^2.30.1",
"notiwind": "^2.0.2",
"papaparse": "^5.4.1",
"pina": "^0.20.2204228",
"pinia-plugin-persistedstate": "^3.2.0",
"pinia-plugin-persistedstate": "^3.2.1",
"qr-code-generator-vue3": "^1.4.21",
"ramda": "^0.29.0",
"readable-stream": "^4.4.2",
"reflect-metadata": "^0.1.13",
"ramda": "^0.29.1",
"readable-stream": "^4.5.2",
"reflect-metadata": "^0.1.14",
"register-service-worker": "^1.7.2",
"simple-vue-camera": "^1.1.3",
"three": "^0.156.1",
"ua-parser-js": "^1.0.37",
"util": "^0.12.5",
"vue": "^3.3.4",
"vue": "^3.4.21",
"vue-axios": "^3.5.2",
"vue-facing-decorator": "^3.0.2",
"vue-qrcode-reader": "^5.4.1",
"vue-router": "^4.2.4",
"vue-facing-decorator": "^3.0.4",
"vue-qrcode-reader": "^5.5.3",
"vue-router": "^4.3.0",
"web-did-resolver": "^2.0.27"
},
"devDependencies": {
"@types/leaflet": "^1.9.4",
"@types/ramda": "^0.29.3",
"@types/leaflet": "^1.9.8",
"@types/ramda": "^0.29.11",
"@types/three": "^0.155.1",
"@types/ua-parser-js": "^0.7.39",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"@typescript-eslint/parser": "^6.6.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vue-leaflet/vue-leaflet": "^0.10.1",
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-eslint": "~5.0.8",
"@vue/cli-plugin-pwa": "~5.0.8",
"@vue/cli-plugin-router": "~5.0.8",
"@vue/cli-plugin-typescript": "~5.0.8",
"@vue/cli-plugin-vuex": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"@vue/eslint-config-typescript": "^11.0.3",
"autoprefixer": "^10.4.15",
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.17.0",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.23.0",
"leaflet": "^1.9.4",
"postcss": "^8.4.29",
"prettier": "^3.1.0",
"tailwindcss": "^3.3.3",
"typescript": "~5.2.2"
"postcss": "^8.4.38",
"prettier": "^3.2.5",
"tailwindcss": "^3.4.1",
"typescript": "~5.2.2",
"vite": "^5.2.0"
}
}

View File

@@ -1,37 +1,46 @@
tasks :
- alert & stop if give amount < 0
- onboarding video
- fix the notification link to the app
- 01 change scanning flow - allow them to stay on the QR/scanning screen after scanning someone
- 24 contextual tutorials https://docs.google.com/document/d/11C_K3RM0rgo0onih20KFhcIzukZyq_CRWqaWX5om_kM/edit#heading=h.iwiwcydou5hw
- 24 Move to Vite assignee:jason
- feeds - add "remote" filter, if they choose 'visible' then warn that they won't see any others, cache list & don't reload front page on change
- .1 add shortcut from project (etc?) to the public project page in a browser
- .1 add KindSpring link to ideas
- .1 on feed, don't show "to someone anonymous" if it's to a project
- .1 on ideas, put an "x" to close it
- 16 save data backups in Google
- 16 generate and use passkeys for identities
- .5 show "give" buttons (eg. from anonymous) even if they can't give, greyed out, and give them a warning and instructions
- .2 when adding a claim on home screen, push that claim to the top of the list
- .2 fix give dialog from "more contacts" off home page to allow giving to this user
- .2 fix bottom of project selection map, where the icons are hidden but a tap goes to the icon's page
- .5 stop from seeing an error on the first page when browser doesn't support service workers (which I've seen on iPhone; visible in Firefox private window)
- .2 don't show a warning on a totally new project when the authorized agent is set
- .2 anchor hash into BTC
- .2 list the "show more" contacts alphabetically
- .5 add back the explicit wait for browser subscription timing problems?
- 32 image on give :
- upload a photo from elsewhere
- go-live - create cert for new image domain, set up haproxy redirect, test-image-api.timesafari.app
- make Time Safari a share_target for images
- .5 make Time Safari a share_target for images
- 08 add image on profile
- ask to detect location & record it in settings
- if personal location is set, show potential local affiliations
- 01 ask to detect location & record it in settings
- 01 if personal location is set, show potential local affiliations
- 02 refactor the buttons for chosing a search location so that the actions are clear assignee-group:ui
- 24 compelling UI for credential presentations
- discover who in my network has activity on a project
- 24 compelling UI for statistics (eg. World?)
- 01 in the feed, group by project or contact or topic or time/$ (via BC)
- 01 in the feed, group by project or contact or topic or time/$ (via BC); new projects, offers, search area, etc assignee-group:ui
- 01 separate not-on-platform vs totally anonymous; terminology "unidentified"?
- .2 add links between projects
- .2 add links between projects assignee-group:ui
- 24 make the contact browsing on the front page something that invites more action
- .5 change server plan & project endpoints to use jwtId as identifier rather than rowid
@@ -57,8 +66,9 @@ tasks :
- .1 hide project-create button on project page if not registered
- .1 hide offer & give buttons on project list page if not registered
- .1 add cursor-pointer on the icons for giving on the project page, and on the list of projects on the discover page
- .2 record when InfiniteScroll hits the end of the list and don't trigger any more loads
- .2 record when InfiniteScroll hits the end of the list and don't trigger any more loads (feed, project list, give & offer lists)
- bug (that is hard to reproduce) - got blank screen and errors on iPhone with no bottom tabs
- bug - turning notifications on from the help screen did not stay, though account screen toggle did stay (From Jason on iPhone.)
- refactor - supply the projectId to the OfferDialog just like we do with the GiftedDialog offerId (in the "open" method, maybe as well as an attribute)
- the confirm button on each give on the ProjectViewView page doesn't have all the context of the ClaimView page, so it can show sometimes inappropriately; consider consolidation
@@ -70,11 +80,9 @@ tasks :
- 01 show my VCs - most interesting, or via search
- 04 allow user to download & prove chains of VCs, mine + ones I can see about me from others
- show feed of offers, new projects, etc -- maybe limited to my search area
- revenue to support server operation
- copy button for seed
- .1 copy button for seed
- .5 If notifications are not enabled, add message to front page with link/button to enable
- make server endpoint for full English description of limits
- create a help-desk document & add screenshots
@@ -92,7 +100,7 @@ tasks :
- .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?
- .2 Show a warning if both giver and recipient are the same (but still allow?)
- 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui
- .5 Shrink the buttons on project pages so they don't expand to the width of the screen assignee-group:ui
- .5 Display a more appealing confirmation on the map when erasing the marker
- .5 remove references to localStorage for projectId (now that it's pulling from the path)
- switch some checks for activeDid to check for isRegistered
@@ -119,10 +127,14 @@ tasks :
- 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
- .5 show seed phrase in a QR code for transfer to another device
- .5 on DiscoverView, switch to a filter UI (eg. just from friend
- .5 on DiscoverView, switch to a filter UI (eg. just from friend)
- .5 don't show "Offer" on project screen if they aren't registered
- 01 especially for iOS, check for new version & update, eg. https://stackoverflow.com/questions/52221805/any-way-yet-to-auto-update-or-just-clear-the-cache-on-a-pwa-on-ios
- 24 allow a person record with interests, including location; purpose? contact methods? enhance other connections the same? (suggestion from Philippines) assignee-group:ui
- 24 brief introduction slides https://docs.google.com/document/d/11C_K3RM0rgo0onih20KFhcIzukZyq_CRWqaWX5om_kM/edit#heading=h.iwiwcydou5hw
- 12 feedback https://docs.google.com/document/d/11C_K3RM0rgo0onih20KFhcIzukZyq_CRWqaWX5om_kM/edit#heading=h.iwiwcydou5hw
- 24 Move to Vite
- 32 accept images for projects
- 32 accept images for contacts
- import project interactions from GitHub/GitLab and manage signing
@@ -136,7 +148,6 @@ tasks :
- for subtasks: fulfills (is it really the same?), feeds, contributes to, supplies, boosts, advances
- for blocking: blocks, precedes, comes before, is sought by -- vs follows, seeks, builds on ("contributes to" isn't specific enough, "succeeds" has different, possibly confusing meaning)
- .5 fit as many icons as possible on home & project view screens but only going halfway down the page assignee-group:ui
- .5 Replace Gifted/Give in ContactsView with GiftedDialog
- Stats :

View File

@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -1,7 +1,7 @@
<template>
<router-view />
<!-- https://github.com/emmanuelsw/notiwind -->
<!-- Messages in the upper-right - https://github.com/emmanuelsw/notiwind -->
<NotificationGroup group="alert">
<div
class="fixed top-4 right-4 w-full max-w-sm flex flex-col items-start justify-end"
@@ -129,6 +129,7 @@
</div>
</NotificationGroup>
<!-- These are general-purpose messages - except there are some for turning app notifications on and off. -->
<NotificationGroup group="modal">
<div class="fixed z-[100] top-0 inset-x-0 w-full">
<Notification
@@ -148,6 +149,7 @@
class="w-full"
role="alert"
>
<!-- type "confirm" will post a message and, with onYes function, show a "Yes" button to call that function -->
<div
v-if="notification.type === 'confirm'"
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
@@ -161,6 +163,7 @@
</p>
<button
v-if="notification.onYes"
@click="
notification.onYes();
close(notification.id);
@@ -174,7 +177,7 @@
@click="close(notification.id)"
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
>
Cancel
{{ notification.onYes ? "Cancel" : "Close" }}
</button>
</div>
</div>
@@ -188,7 +191,7 @@
>
<div class="w-full px-6 py-6 text-slate-900 text-center">
<p v-if="serviceWorkerReady" class="text-lg mb-4">
Would you like to <b>turn on</b> notifications for this app?
Would you like to be notified of new activity once a day?
</p>
<p v-else class="text-lg mb-4">
Waiting for system initialization, which may take up to 10
@@ -196,22 +199,42 @@
<fa icon="spinner" spin />
</p>
<button
v-if="serviceWorkerReady"
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
@click="
close(notification.id);
turnOnNotifications();
"
>
Turn on Notifications
</button>
<div v-if="serviceWorkerReady">
<span class="flex flex-row justify-center">
<span class="mt-2">Yes, tell me at: </span>
<input
type="number"
class="rounded-l border border-r-0 border-slate-400 ml-2 mt-2 px-2 py-2 text-center w-20"
v-model="hourInput"
/>
<span
class="rounded-r border border-slate-400 bg-slate-200 text-center text-blue-500 mt-2 px-2 py-2 w-20"
@click="hourAm = !hourAm"
>
<span v-if="hourAm"> AM <fa icon="chevron-down" /> </span>
<span v-else> PM <fa icon="chevron-up" /> </span>
</span>
</span>
<button
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white mt-2 px-2 py-2 rounded-md"
@click="
() => {
if (checkHour()) {
close(notification.id);
turnOnNotifications();
}
}
"
>
Turn on Daily Message
</button>
</div>
<button
@click="close(notification.id)"
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white mt-4 px-2 py-2 rounded-md"
>
Maybe Later
No, Not Now
</button>
</div>
</div>
@@ -294,8 +317,11 @@
<style></style>
<script lang="ts">
import { Vue, Component } from "vue-facing-decorator";
import axios from "axios";
import { Vue, Component } from "vue-facing-decorator";
import * as libsUtil from "@/libs/util";
interface ServiceWorkerMessage {
type: string;
data: string;
@@ -319,6 +345,10 @@ interface VapidResponse {
};
}
interface PushSubscriptionWithTime extends PushSubscriptionJSON {
notifyTime: { utcHour: number };
}
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@@ -329,7 +359,9 @@ export default class App extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
b64 = "";
serviceWorkerReady = false;
hourAm = true;
hourInput = "8";
serviceWorkerReady = true;
async mounted() {
try {
@@ -340,25 +372,29 @@ export default class App extends Vue {
pushUrl = settings.webPushServer;
}
await axios
.get(pushUrl + "/web-push/vapid")
.then((response: VapidResponse) => {
this.b64 = response.data?.vapidKey || "";
console.log("Got vapid key:", this.b64);
navigator.serviceWorker.addEventListener("controllerchange", () => {
console.log("New service worker is now controlling the page");
if (pushUrl.startsWith("http://localhost")) {
console.log("Not checking for VAPID in this local environment.");
} else {
await axios
.get(pushUrl + "/web-push/vapid")
.then((response: VapidResponse) => {
this.b64 = response.data?.vapidKey || "";
console.log("Got vapid key:", this.b64);
navigator.serviceWorker.addEventListener("controllerchange", () => {
console.log("New service worker is now controlling the page");
});
});
});
if (!this.b64) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Setting Notifications",
text: "Could not set notifications.",
},
-1,
);
if (!this.b64) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Setting Notifications",
text: "Could not set notifications.",
},
-1,
);
}
}
} catch (error) {
if (window.location.host.startsWith("localhost")) {
@@ -458,6 +494,48 @@ export default class App extends Vue {
});
}
// this allows us to show an error without closing the dialog
checkHour() {
if (!libsUtil.isNumeric(this.hourInput)) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Not a Number",
text: "The time must be an hour number.",
},
5000,
);
return false;
}
const hourNum = libsUtil.numberOrZero(this.hourInput);
if (!Number.isInteger(hourNum)) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Not a Whole Number",
text: "The time must be a whole hour number.",
},
5000,
);
return false;
}
if (hourNum < 1 || 12 < hourNum) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Not a Whole Number",
text: "The time must be an hour between 1 and 12.",
},
5000,
);
return false;
}
return true;
}
public async turnOnNotifications() {
return this.askPermission()
.then((permission) => {
@@ -483,13 +561,25 @@ export default class App extends Vue {
},
-1,
);
this.sendSubscriptionToServer(subscription);
return subscription;
// we already checked that this is a valid hour number
const rawHourNum = libsUtil.numberOrZero(this.hourInput);
const adjHourNum = rawHourNum + (this.hourAm ? 0 : 12);
const hourNum = adjHourNum % 24;
const utcHour =
hourNum + Math.round(new Date().getTimezoneOffset() / 60);
const finalUtcHour = (utcHour + (utcHour < 0 ? 24 : 0)) % 24;
const subscriptionWithTime: PushSubscriptionWithTime = {
notifyTime: { utcHour: finalUtcHour },
...subscription.toJSON(),
};
await this.sendSubscriptionToServer(subscriptionWithTime);
return subscriptionWithTime;
} else {
throw new Error("Subscription object is not available.");
}
})
.then(async (subscription) => {
.then(async (subscription: PushSubscriptionWithTime) => {
console.log(
"Subscription data sent to server and all finished successfully.",
);
@@ -580,7 +670,7 @@ export default class App extends Vue {
}
private sendSubscriptionToServer(
subscription: PushSubscription,
subscription: PushSubscriptionWithTime,
): Promise<void> {
console.log("About to send subscription...", subscription);
return fetch("/web-push/subscribe", {

View File

@@ -1,9 +1,8 @@
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap');
@layer base {
html {
font-family: 'Work Sans', ui-sans-serif, system-ui, sans-serif !important;

View File

@@ -0,0 +1,219 @@
<template>
<div v-if="visible" id="dialogFeedFilters" class="dialog-overlay">
<div class="dialog">
<h1 class="text-xl font-bold text-center mb-4">Feed Filters</h1>
<p class="mb-4 font-bold">Show only activities that</p>
<div class="grid grid-cols-1 gap-2">
<div
class="flex items-center justify-between cursor-pointer"
@click="toggleHasVisibleDid()"
>
<!-- label -->
<div>Include someone visible to me</div>
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input
type="checkbox"
v-model="hasVisibleDid"
name="toggleFilterFromMyContacts"
class="sr-only"
/>
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
</div>
<em>or</em>
<div
class="flex items-center justify-between cursor-pointer"
@click="
hasSearchBox
? toggleNearby()
: $router.push({ name: 'search-area' })
"
>
<!-- label -->
<div>Are nearby</div>
<!-- toggle -->
<div v-if="hasSearchBox" class="relative ml-2">
<!-- input -->
<input
type="checkbox"
v-model="isNearby"
name="toggleFilterNearby"
class="sr-only"
/>
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
<div v-else class="relative ml-2">
<button class="ml-2 px-4 py-2 rounded-md bg-blue-200 text-blue-500">
Select Location
</button>
</div>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-2 mt-4">
<button
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="setAll()"
>
Set All
</button>
<button
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="clearAll()"
>
Clear All
</button>
<button
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="done()"
>
Done
</button>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-facing-decorator";
import {
LMap,
LMarker,
LRectangle,
LTileLayer,
} from "@vue-leaflet/vue-leaflet";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { db } from "@/db/index";
@Component({
components: {
LRectangle,
LMap,
LMarker,
LTileLayer,
},
})
export default class FeedFilters extends Vue {
onCloseIfChanged = () => {};
hasSearchBox = false;
hasVisibleDid = false;
isNearby = false;
settingChanged = false;
visible = false;
async open(onCloseIfChanged: () => void) {
this.onCloseIfChanged = onCloseIfChanged;
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.hasVisibleDid = !!settings?.filterFeedByVisible;
this.isNearby = !!settings?.filterFeedByNearby;
if (settings?.searchBoxes && settings.searchBoxes.length > 0) {
this.hasSearchBox = true;
}
this.settingChanged = false;
this.visible = true;
}
toggleHasVisibleDid() {
this.settingChanged = true;
this.hasVisibleDid = !this.hasVisibleDid;
db.settings.update(MASTER_SETTINGS_KEY, {
filterFeedByVisible: this.hasVisibleDid,
});
}
toggleNearby() {
this.settingChanged = true;
this.isNearby = !this.isNearby;
db.settings.update(MASTER_SETTINGS_KEY, {
filterFeedByNearby: this.isNearby,
});
}
async clearAll() {
if (this.hasVisibleDid || this.isNearby) {
this.settingChanged = true;
}
db.settings.update(MASTER_SETTINGS_KEY, {
filterFeedByNearby: false,
filterFeedByVisible: false,
});
this.hasVisibleDid = false;
this.isNearby = false;
}
async setAll() {
if (!this.hasVisibleDid || !this.isNearby) {
this.settingChanged = true;
}
db.settings.update(MASTER_SETTINGS_KEY, {
filterFeedByNearby: true,
filterFeedByVisible: true,
});
this.hasVisibleDid = true;
this.isNearby = true;
}
close() {
if (this.settingChanged) {
this.onCloseIfChanged();
}
this.visible = false;
}
done() {
this.close();
}
}
</script>
<style>
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
padding: 1.5rem;
}
#dialogFeedFilters.dialog-overlay {
z-index: 99999;
overflow: scroll;
}
.dialog {
background-color: white;
padding: 1rem;
border-radius: 0.5rem;
width: 100%;
max-width: 500px;
}
</style>

View File

@@ -53,25 +53,32 @@
}"
class="text-blue-500"
>
More Options
Photo, ...
</router-link>
</span>
</div>
<p class="text-center mb-2 mt-6 italic">
Sign & Send to publish to the world
<fa
icon="circle-info"
class="pl-2 text-blue-500 cursor-pointer"
@click="explainData()"
/>
</p>
<button
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
@click="confirm"
>
Sign &amp; Send
</button>
<button
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
@click="cancel"
>
Cancel
</button>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
@click="confirm"
>
Sign &amp; Send
</button>
<button
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="cancel"
>
Cancel
</button>
</div>
</div>
</div>
</template>
@@ -195,6 +202,45 @@ export default class GiftedDialog extends Vue {
}
async confirm() {
if (!this.activeDid) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "You must select an identifier before you can record a give.",
},
3000,
);
return;
}
if (parseFloat(this.amountInput) < 0) {
this.$notify(
{
group: "alert",
type: "danger",
text: "You may not send a negative number.",
title: "",
},
2000,
);
return;
}
if (!this.description && !parseFloat(this.amountInput)) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: `You must enter a description or some number of ${
this.libsUtil.UNIT_LONG[this.unitCode]
}.`,
},
2000,
);
return;
}
this.close();
this.$notify(
{
@@ -229,32 +275,6 @@ export default class GiftedDialog extends Vue {
amountInput: number,
unitCode: string = "HUR",
) {
if (!this.activeDid) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "You must select an identifier before you can record a give.",
},
-1,
);
return;
}
if (!description && !amountInput) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: `You must enter a description or some number of ${this.libsUtil.UNIT_LONG[unitCode]}.`,
},
-1,
);
return;
}
try {
const identity = await libsUtil.getIdentity(this.activeDid);
const result = await createAndSubmitGive(
@@ -339,6 +359,18 @@ export default class GiftedDialog extends Vue {
result.response?.data?.error?.message
);
}
explainData() {
this.$notify(
{
group: "alert",
type: "success",
title: "Data Sharing",
text: libsUtil.PRIVACY_MESSAGE,
},
-1,
);
}
}
</script>

View File

@@ -1,57 +1,85 @@
<template>
<div v-if="visible" class="dialog-overlay">
<!-- Breadcrumb -->
<div class="dialog">
<!-- Back -->
<div class="text-lg text-center font-light relative px-7">
<h1
class="text-lg text-center px-2 py-1 absolute -right-2 -top-1"
<div v-if="visible" class="dialog-overlay z-[60]">
<div class="dialog relative">
<div class="text-lg text-center font-light relative z-50">
<div
id="ViewHeading"
class="text-center font-bold absolute top-0 left-0 right-0 px-4 py-2 bg-black/50 text-white leading-none"
>
<span v-if="uploading"> Uploading... </span>
<span v-else-if="blob"> Look Good? </span>
<span v-else> Say "Cheese"! </span>
</div>
<div
class="text-lg text-center p-2 leading-none absolute right-0 top-0 text-white"
@click="close()"
>
<fa icon="xmark" class="fa-fw"></fa>
</h1>
<fa icon="xmark" class="w-[1em]"></fa>
</div>
</div>
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4">
<span v-if="uploading"> Uploading... </span>
<span v-else-if="blob"> Look Good? </span>
<span v-else> Say "Cheese"! </span>
</h1>
<div v-if="uploading" class="flex justify-center">
<fa icon="spinner" class="fa-spin fa-3x text-center block" />
</div>
<div v-else-if="blob">
<div class="flex justify-around">
<div
class="flex justify-center gap-2 absolute bottom-[1rem] left-[1rem] right-[1rem] bg-black/50 px-4 py-2"
>
<button
@click="uploadImage"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-2 rounded-full"
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white font-bold py-2 px-4 rounded-md"
>
<span>Upload</span>
</button>
<button
@click="retryImage"
class="bg-slate-500 hover:bg-slate-700 text-white font-bold py-2 px-2 rounded-full"
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white font-bold py-2 px-4 rounded-md"
>
<span>Retry</span>
</button>
</div>
<img :src="URL.createObjectURL(blob)" class="mt-2 w-full" />
<div class="flex justify-center">
<img :src="URL.createObjectURL(blob)" class="mt-2 rounded" />
</div>
</div>
<div v-else>
<div v-else ref="cameraContainer">
<!--
Camera "resolution" doesn't change how it shows on screen but rather stretches the result, eg the following which just stretches it vertically:
:resolution="{ width: 375, height: 812 }"
-->
<camera facingMode="environment" autoplay ref="camera">
<div class="absolute bottom-0 w-full flex justify-center pb-4">
<!-- Button -->
<camera
facingMode="environment"
autoplay
ref="camera"
@started="cameraStarted()"
>
<div
class="absolute portrait:bottom-0 portrait:left-0 portrait:right-0 portrait:pb-2 landscape:right-0 landscape:top-0 landscape:bottom-0 landscape:pr-4 flex landscape:flex-row justify-center items-center"
>
<button
@click="takeImage"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-2 rounded-full"
@click="takeImage()"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded-full text-2xl leading-none"
>
<fa icon="camera" class="fa-fw"></fa>
<fa icon="camera" class="w-[1em]"></fa>
</button>
</div>
<div
class="absolute portrait:bottom-2 portrait:right-16 landscape:right-0 landscape:bottom-16 landscape:pr-4 flex justify-center items-center"
>
<button
@click="swapMirrorClass()"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded-full text-2xl leading-none"
>
<fa icon="left-right" class="w-[1em]"></fa>
</button>
</div>
<div v-if="numDevices > 1" class="absolute bottom-2 right-4">
<button
@click="switchCamera()"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded-full text-2xl leading-none"
>
<fa icon="rotate" class="w-[1em]"></fa>
</button>
</div>
</camera>
@@ -75,8 +103,11 @@ import { accessToken } from "@/libs/crypto";
export default class GiftedPhotoDialog extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
activeDeviceNumber = 0;
activeDid = "";
blob: Blob | null = null;
mirror = false;
numDevices = 0;
setImage: (arg: string) => void = () => {};
uploading = false;
visible = false;
@@ -105,17 +136,79 @@ export default class GiftedPhotoDialog extends Vue {
open(setImageFn: (arg: string) => void) {
this.visible = true;
const bottomNav = document.querySelector("#QuickNav") as HTMLElement;
if (bottomNav) {
bottomNav.style.display = "none";
}
this.setImage = setImageFn;
}
close() {
this.visible = false;
const bottomNav = document.querySelector("#QuickNav") as HTMLElement;
if (bottomNav) {
bottomNav.style.display = "";
}
this.blob = null;
}
async cameraStarted() {
const cameraComponent = this.$refs.camera as InstanceType<typeof Camera>;
if (cameraComponent) {
this.numDevices = (await cameraComponent.devices(["videoinput"])).length;
this.mirror = cameraComponent.facingMode === "user";
}
}
async switchCamera() {
const cameraComponent = this.$refs.camera as InstanceType<typeof Camera>;
this.activeDeviceNumber = (this.activeDeviceNumber + 1) % this.numDevices;
const devices = await cameraComponent?.devices(["videoinput"]);
cameraComponent?.changeCamera(devices[this.activeDeviceNumber].deviceId);
}
async takeImage(/* payload: MouseEvent */) {
const cameraComponent = this.$refs.camera as InstanceType<typeof Camera>;
this.blob = await cameraComponent?.snapshot(); // png is default; if that changes, change extension in formData.append
/**
* This logic to set the image height & width correctly.
* Without it, the portrait orientation ends up with an image that is stretched horizontally.
* Note that it's the same with raw browser Javascript; see the "drawImage" example below.
* Now that I've done it, I can't explain why it works.
*/
let imageHeight = cameraComponent?.resolution?.height;
let imageWidth = cameraComponent?.resolution?.width;
const initialImageRatio = imageWidth / imageHeight;
const windowRatio = window.innerWidth / window.innerHeight;
if (initialImageRatio > 1 && windowRatio < 1) {
// the image is wider than it is tall, and the window is taller than it is wide
// For some reason, mobile in portrait orientation renders a horizontally-stretched image.
// We're gonna force it opposite.
imageHeight = cameraComponent?.resolution?.width;
imageWidth = cameraComponent?.resolution?.height;
} else if (initialImageRatio < 1 && windowRatio > 1) {
// the image is taller than it is wide, and the window is wider than it is tall
// Haven't seen this happen, but we'll do it just in case.
imageHeight = cameraComponent?.resolution?.width;
imageWidth = cameraComponent?.resolution?.height;
}
const newImageRatio = imageWidth / imageHeight;
if (newImageRatio < windowRatio) {
// the image is a taller ratio than the window, so fit the height first
imageHeight = window.innerHeight / 2;
imageWidth = imageHeight * newImageRatio;
} else {
// the image is a wider ratio than the window, so fit the width first
imageWidth = window.innerWidth / 2;
imageHeight = imageWidth / newImageRatio;
}
// The resolution is only necessary because of that mobile portrait-orientation case.
// The mobile emulation in a browser shows something stretched vertically, but real devices work fine.
this.blob = await cameraComponent?.snapshot({
height: imageHeight,
width: imageWidth,
}); // png is default; if that changes, change extension in formData.append
if (!this.blob) {
this.$notify(
{
@@ -134,6 +227,51 @@ export default class GiftedPhotoDialog extends Vue {
this.blob = null;
}
/****
Here's an approach to photo capture without a library. It has similar quirks.
Now that we've fixed styling for simple-vue-camera, it's not critical to refactor. Maybe someday.
<button id="start-camera" @click="cameraClicked">Start Camera</button>
<video id="video" width="320" height="240" autoplay></video>
<button id="snap-photo" @click="photoSnapped">Snap Photo</button>
<canvas id="canvas" width="320" height="240"></canvas>
async cameraClicked() {
const video = document.querySelector("#video");
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false,
});
if (video instanceof HTMLVideoElement) {
video.srcObject = stream;
}
}
photoSnapped() {
const video = document.querySelector("#video");
const canvas = document.querySelector("#canvas");
if (
canvas instanceof HTMLCanvasElement &&
video instanceof HTMLVideoElement
) {
canvas
?.getContext("2d")
?.drawImage(video, 0, 0, canvas.width, canvas.height);
// ... or set the blob:
// canvas?.toBlob(
// (blob) => {
// this.blob = blob;
// },
// "image/jpeg",
// 1,
// );
// data url of the image
const image_data_url = canvas?.toDataURL("image/jpeg");
}
}
****/
async uploadImage() {
this.uploading = true;
const identifier = await getIdentity(this.activeDid);
@@ -184,6 +322,17 @@ export default class GiftedPhotoDialog extends Vue {
this.blob = null;
}
}
swapMirrorClass() {
this.mirror = !this.mirror;
if (this.mirror) {
(this.$refs.cameraContainer as HTMLElement).classList.add("mirror-video");
} else {
(this.$refs.cameraContainer as HTMLElement).classList.remove(
"mirror-video",
);
}
}
}
</script>
@@ -208,4 +357,12 @@ export default class GiftedPhotoDialog extends Vue {
width: 100%;
max-width: 700px;
}
.mirror-video {
transform: scaleX(-1);
-webkit-transform: scaleX(-1); /* For Safari */
-moz-transform: scaleX(-1); /* For Firefox */
-ms-transform: scaleX(-1); /* For IE */
-o-transform: scaleX(-1); /* For Opera */
}
</style>

View File

@@ -1,7 +1,15 @@
<template>
<div v-if="visible" class="dialog-overlay">
<div class="dialog">
<h1 class="text-xl font-bold text-center mb-4">Here's one:</h1>
<h1 class="text-xl font-bold text-center mb-4 relative">
Here's one:
<div
class="text-lg text-center p-2 leading-none absolute right-0 -top-1"
@click="cancel"
>
<fa icon="xmark" class="w-[1em]"></fa>
</div>
</h1>
<span class="flex justify-between">
<span
class="rounded-l border border-slate-400 bg-slate-200 px-4 py-2 flex"
@@ -33,7 +41,7 @@
<span class="flex justify-between">
<span />
<button
class="text-center bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4"
class="text-center bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4"
@click="nextIdeaPastContacts()"
>
Skip Contacts <fa icon="forward" />
@@ -52,7 +60,7 @@
</span>
</span>
<button
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4"
@click="cancel"
>
That's it!

View File

@@ -50,18 +50,20 @@
<p class="text-center mt-6 mb-2 italic">
Sign & Send to publish to the world
</p>
<button
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
@click="confirm"
>
Sign &amp; Send
</button>
<button
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
@click="cancel"
>
Cancel
</button>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
@click="confirm"
>
Sign &amp; Send
</button>
<button
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="cancel"
>
Cancel
</button>
</div>
</div>
</div>
</template>

View File

@@ -20,11 +20,11 @@ export enum AppString {
}
export const DEFAULT_ENDORSER_API_SERVER =
process.env.VUE_APP_DEFAULT_ENDORSER_API_SERVER ||
import.meta.env.VITE_DEFAULT_ENDORSER_API_SERVER ||
AppString.TEST_ENDORSER_API_SERVER;
export const DEFAULT_IMAGE_API_SERVER =
process.env.VUE_APP_DEFAULT_IMAGE_API_SERVER ||
import.meta.env.VITE_DEFAULT_IMAGE_API_SERVER ||
AppString.TEST_IMAGE_API_SERVER;
export const DEFAULT_PUSH_SERVER =

View File

@@ -16,6 +16,10 @@ export type Settings = {
activeDid?: string; // Active Decentralized ID
apiServer?: string; // API server URL
filterFeedByNearby?: boolean; // filter by nearby
filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden
firstName?: string; // User's first name
isRegistered?: boolean;
lastName?: string; // deprecated - put all names in firstName
@@ -38,6 +42,10 @@ export type Settings = {
webPushServer?: string; // Web Push server URL
};
export function isAnyFeedFilterOn(settings: Settings): boolean {
return !!(settings.filterFeedByNearby || settings.filterFeedByVisible);
}
/**
* Schema for the Settings table in the database.
*/

View File

@@ -113,7 +113,7 @@ export const sign = async (privateKeyHex: string) => {
* The SimpleSigner returns a configured function for signing data.
*
* @example
* const signer = SimpleSigner(process.env.PRIVATE_KEY)
* const signer = SimpleSigner(import.meta.env.PRIVATE_KEY)
* signer(data, (err, signature) => {
* ...
* })

View File

@@ -1,9 +1,11 @@
import { Axios, AxiosResponse, RawAxiosRequestHeaders } from "axios";
import * as didJwt from "did-jwt";
import { LRUCache } from "lru-cache";
import * as R from "ramda";
import { IIdentifier } from "@veramo/core";
import { accessToken, SimpleSigner } from "@/libs/crypto";
import * as didJwt from "did-jwt";
import { Axios, AxiosResponse } from "axios";
import { Contact } from "@/db/tables/contacts";
import { accessToken, SimpleSigner } from "@/libs/crypto";
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
// the object in RegisterAction claims
@@ -49,7 +51,7 @@ export interface GenericVerifiableCredential {
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}
export interface GenericServerRecord extends GenericVerifiableCredential {
export interface GenericCredWrapper extends GenericVerifiableCredential {
handleId?: string;
id: string;
issuedAt: string;
@@ -58,7 +60,7 @@ export interface GenericServerRecord extends GenericVerifiableCredential {
claim: Record<string, any>;
claimType?: string;
}
export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = {
export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper = {
"@context": SCHEMA_ORG_CONTEXT,
"@type": "",
claim: {},
@@ -68,7 +70,7 @@ export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = {
};
// a summary record; the VC is found the fullClaim field
export interface GiveServerRecord {
export interface GiveSummaryRecord {
agentDid: string;
amount: number;
amountConfirmed: number;
@@ -83,7 +85,7 @@ export interface GiveServerRecord {
}
// a summary record; the VC is found the fullClaim field
export interface OfferServerRecord {
export interface OfferSummaryRecord {
amount: number;
amountGiven: number;
amountGivenConfirmed: number;
@@ -101,7 +103,7 @@ export interface OfferServerRecord {
}
// a summary record; the VC is not currently part of this record
export interface PlanServerRecord {
export interface PlanSummaryRecord {
agentDid?: string; // optional, if the issuer wants someone else to manage as well
description: string;
endTime?: string;
@@ -110,6 +112,7 @@ export interface PlanServerRecord {
issuerDid: string;
locLat?: number;
locLon?: number;
name?: string;
startTime?: string;
url?: string;
}
@@ -256,6 +259,10 @@ export type CreateAndSubmitClaimResult = SuccessResult | ErrorResult;
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
const HIDDEN_DID = "did:none:HIDDEN";
const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
max: 500,
});
export function isDid(did: string) {
return did.startsWith("did:");
}
@@ -269,7 +276,7 @@ export function isEmptyOrHiddenDid(did?: string) {
}
/**
* @return true for any nested string where func(input) === true
* @return true for any string within this primitive/object/array where func(input) === true
*
* Similar logic is found in endorser-mobile.
*/
@@ -304,6 +311,12 @@ export function containsHiddenDid(obj: any) {
return testRecursivelyOnStrings(isHiddenDid, obj);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const containsNonHiddenDid = (obj: any) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return testRecursivelyOnStrings((s: any) => isDid(s) && !isHiddenDid(s), obj);
};
export function stripEndorserPrefix(claimId: string) {
if (claimId && claimId.startsWith(ENDORSER_CH_HANDLE_PREFIX)) {
return claimId.substring(ENDORSER_CH_HANDLE_PREFIX.length);
@@ -403,8 +416,11 @@ export function didInfoForContact(
return myId
? { displayName: "You (Alt ID)", known: true }
: isHiddenDid(did)
? { displayName: "Someone Outside Your Network", known: false }
: { displayName: "Someone Outside Contacts", known: false };
? { displayName: "Someone Totally Outside Your View", known: false }
: {
displayName: "Someone Visible But Outside Your Contact List",
known: false,
};
}
}
@@ -423,6 +439,71 @@ export function didInfo(
return didInfoForContact(did, activeDid, contact, allMyDids).displayName;
}
async function getHeaders(identity: IIdentifier | null) {
const headers: RawAxiosRequestHeaders = {
"Content-Type": "application/json",
};
if (identity) {
const token = await accessToken(identity);
headers["Authorization"] = "Bearer " + token;
}
return headers;
}
/**
* @param handleId nullable -- which means that "undefined" will be returned
* @param identity nullable -- which means no private info will be returned
* @param axios
* @param apiServer
*/
export async function getPlanFromCache(
handleId: string | null,
identity: IIdentifier | null,
axios: Axios,
apiServer: string,
) {
if (!handleId) {
return undefined;
}
let cred = planCache.get(handleId);
if (!cred) {
const url =
apiServer +
"/api/v2/report/plans?handleId=" +
encodeURIComponent(handleId);
const headers = await getHeaders(identity);
try {
const resp = await axios.get(url, { headers });
if (resp.status === 200 && resp.data?.data?.length > 0) {
cred = resp.data.data[0];
planCache.set(handleId, cred);
} else {
console.log(
"Failed to load plan with handle",
handleId,
" Got data:",
resp.data,
);
}
} catch (error) {
console.log(
"Failed to load plan with handle",
handleId,
" Got error:",
error,
);
}
}
return cred;
}
export async function setPlanInCache(
handleId: string,
planSummary: PlanSummaryRecord,
) {
planCache.set(handleId, planSummary);
}
/**
* For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
*
@@ -475,7 +556,7 @@ export async function createAndSubmitGive(
vcClaim.image = imageUrl;
}
return createAndSubmitClaim(
vcClaim as GenericServerRecord,
vcClaim as GenericCredWrapper,
identity,
apiServer,
axios,
@@ -524,7 +605,7 @@ export async function createAndSubmitOffer(
};
}
return createAndSubmitClaim(
vcClaim as GenericServerRecord,
vcClaim as GenericCredWrapper,
identity,
apiServer,
axios,
@@ -695,7 +776,7 @@ const claimSummary = (claim: Record<string, any>) => {
similar code is also contained in endorser-mobile
**/
export const claimSpecialDescription = (
record: GenericServerRecord,
record: GenericCredWrapper,
activeDid: string,
identifiers: Array<string>,
contacts: Array<Contact>,
@@ -789,12 +870,12 @@ export const claimSpecialDescription = (
"...]"
);
} else {
return issuer + " declared " + claimSummary(claim as GenericServerRecord);
return issuer + " declared " + claimSummary(claim as GenericCredWrapper);
}
};
export const BVC_MEETUPS_PROJECT_CLAIM_ID =
process.env.VUE_APP_BVC_MEETUPS_PROJECT_CLAIM_ID ||
import.meta.env.VITE_BVC_MEETUPS_PROJECT_CLAIM_ID ||
"https://endorser.ch/entity/01HNTZYJJXTGT0EZS3VEJGX7AK"; // this won't resolve as a URL on production; it's a URN only found in the test system
export const bvcMeetingJoinClaim = (did: string, startTime: string) => {

View File

@@ -9,16 +9,11 @@ import { accountsDB, db } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
import { GenericServerRecord, containsHiddenDid } from "@/libs/endorserServer";
import { GenericCredWrapper, containsHiddenDid } from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
// If you edit this, check that the numbers still line up on the side in the alert (on mobile, too),
// and make sure they can take all actions while the notification shows.
export const ONBOARD_MESSAGE =
"1) Read through all their yellow prompts. 2) Add them to your Contacts by scanning with the QR icon that is by the input box. 3) Click the person icon to register them. 4) Show them your QR so they'll scan you. 5) Have them enable notifications.";
export const PRIVACY_MESSAGE =
"The data you send be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to those you allow.";
/* eslint-disable prettier/prettier */
export const UNIT_SHORT: Record<string, string> = {
@@ -58,9 +53,13 @@ export function iconForUnitCode(unitCode: string) {
}
// from https://stackoverflow.com/a/175787/845494
// ... though it appears even this isn't precisely right so keep doing "|| 0" or something in sensitive places
//
export function isNumeric(str: string): boolean {
return !isNaN(+str);
// This ignore commentary is because typescript complains when you pass a string to isNaN.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return !isNaN(str) && !isNaN(parseFloat(str));
}
export function numberOrZero(str: string): number {
@@ -71,7 +70,7 @@ export const isGlobalUri = (uri: string) => {
return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/));
};
export const giveIsConfirmable = (veriClaim: GenericServerRecord) => {
export const giveIsConfirmable = (veriClaim: GenericCredWrapper) => {
return veriClaim.claimType === "GiveAction";
};
@@ -87,7 +86,7 @@ export const doCopyTwoSecRedo = (text: string, fn: () => void) => {
* @param veriClaim is expected to have fields: claim, claimType, and issuer
*/
export const isGiveRecordTheUserCanConfirm = (
veriClaim: GenericServerRecord,
veriClaim: GenericCredWrapper,
activeDid: string,
confirmerIdList: string[] = [],
) => {
@@ -103,9 +102,9 @@ export const isGiveRecordTheUserCanConfirm = (
* @returns the DID of the person who offered, or undefined if hidden
* @param veriClaim is expected to have fields: claim and issuer
*/
export const offerGiverDid: (
arg0: GenericServerRecord,
) => string | undefined = (veriClaim) => {
export const offerGiverDid: (arg0: GenericCredWrapper) => string | undefined = (
veriClaim,
) => {
let giver;
if (
veriClaim.claim.offeredBy?.identifier &&
@@ -122,7 +121,7 @@ export const offerGiverDid: (
* @returns true if the user can fulfill the offer
* @param veriClaim is expected to have fields: claim, claimType, and issuer
*/
export const canFulfillOffer = (veriClaim: GenericServerRecord) => {
export const canFulfillOffer = (veriClaim: GenericCredWrapper) => {
return !!(veriClaim.claimType === "Offer" && offerGiverDid(veriClaim));
};
@@ -239,7 +238,7 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
};
export const sendTestThroughPushServer = async (
subscription: PushSubscription,
subscriptionJSON: PushSubscriptionJSON,
skipFilter: boolean,
): Promise<AxiosResponse> => {
await db.open();
@@ -254,28 +253,11 @@ export const sendTestThroughPushServer = async (
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
const auth = Buffer.from(subscription.getKey("auth"));
const authB64 = auth
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const p256dh = Buffer.from(subscription.getKey("p256dh"));
const p256dhB64 = p256dh
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const newPayload = {
endpoint: subscription.endpoint,
keys: {
auth: authB64,
p256dh: p256dhB64,
},
message: `Test, where you will see this message ${
skipFilter ? "un" : ""
}filtered.`,
// eslint-disable-next-line prettier/prettier
message: `Test, where you will see this message ${ skipFilter ? "un" : "" }filtered.`,
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
...subscriptionJSON,
};
console.log("Sending a test web push message:", newPayload);
const payloadStr = JSON.stringify(newPayload);

View File

@@ -20,8 +20,10 @@ import {
faCalendar,
faCamera,
faCheck,
faChevronDown,
faChevronLeft,
faChevronRight,
faChevronUp,
faCircle,
faCircleCheck,
faCircleInfo,
@@ -45,6 +47,7 @@ import {
faHand,
faHandHoldingHeart,
faHouseChimney,
faLeftRight,
faLocationDot,
faLongArrowAltLeft,
faLongArrowAltRight,
@@ -80,8 +83,10 @@ library.add(
faCalendar,
faCamera,
faCheck,
faChevronDown,
faChevronLeft,
faChevronRight,
faChevronUp,
faCircle,
faCircleCheck,
faCircleInfo,
@@ -105,6 +110,7 @@ library.add(
faHand,
faHandHoldingHeart,
faHouseChimney,
faLeftRight,
faLocationDot,
faLongArrowAltLeft,
faLongArrowAltRight,

View File

@@ -2,7 +2,7 @@
import { register } from "register-service-worker";
if (process.env.NODE_ENV === "production") {
if (import.meta.env.NODE_ENV === "production") {
register("/sw_scripts-combined.js", {
ready() {
console.log(

View File

@@ -31,221 +31,159 @@ const routes: Array<RouteRecordRaw> = [
{
path: "/account",
name: "account",
component: () =>
import(/* webpackChunkName: "account" */ "../views/AccountViewView.vue"),
component: () => import("../views/AccountViewView.vue"),
},
{
path: "/claim/:id?",
name: "claim",
component: () =>
import(/* webpackChunkName: "claim" */ "../views/ClaimView.vue"),
component: () => import("../views/ClaimView.vue"),
},
{
path: "/confirm-contact",
name: "confirm-contact",
component: () =>
import(
/* webpackChunkName: "confirm-contact" */ "../views/ConfirmContactView.vue"
),
component: () => import("../views/ConfirmContactView.vue"),
},
{
path: "/contact-amounts",
name: "contact-amounts",
component: () =>
import(
/* webpackChunkName: "contact-amounts" */ "../views/ContactAmountsView.vue"
),
component: () => import("../views/ContactAmountsView.vue"),
},
{
path: "/contact-gives",
name: "contact-gives",
component: () =>
import(
/* webpackChunkName: "contact-gives" */ "../views/ContactGiftingView.vue"
),
component: () => import("../views/ContactGiftingView.vue"),
},
{
path: "/contact-qr",
name: "contact-qr",
component: () =>
import(
/* webpackChunkName: "contact-qr" */ "../views/ContactQRScanShowView.vue"
),
component: () => import("../views/ContactQRScanShowView.vue"),
},
{
path: "/contacts",
name: "contacts",
component: () =>
import(/* webpackChunkName: "contacts" */ "../views/ContactsView.vue"),
component: () => import("../views/ContactsView.vue"),
},
{
path: "/discover",
name: "discover",
component: () =>
import(/* webpackChunkName: "discover" */ "../views/DiscoverView.vue"),
component: () => import("../views/DiscoverView.vue"),
},
{
path: "/gifted-details",
name: "gifted-details",
component: () =>
import(
/* webpackChunkName: "gifted-details" */ "../views/GiftedDetails.vue"
),
component: () => import("../views/GiftedDetails.vue"),
},
{
path: "/help",
name: "help",
component: () =>
import(/* webpackChunkName: "help" */ "../views/HelpView.vue"),
},
{
path: "/",
name: "home",
component: () =>
import(/* webpackChunkName: "home" */ "../views/HomeView.vue"),
component: () => import("../views/HelpView.vue"),
},
{
path: "/help-notifications",
name: "help-notifications",
component: () =>
import(
/* webpackChunkName: "help-notifications" */ "../views/HelpNotificationsView.vue"
),
component: () => import("../views/HelpNotificationsView.vue"),
},
{
path: "/help-onboarding",
name: "help-onboarding",
component: () => import("../views/HelpOnboardingView.vue"),
},
{
path: "/",
name: "home",
component: () => import("../views/HomeView.vue"),
},
{
path: "/identity-switcher",
name: "identity-switcher",
component: () =>
import(
/* webpackChunkName: "identity-switcher" */ "../views/IdentitySwitcherView.vue"
),
component: () => import("../views/IdentitySwitcherView.vue"),
},
{
path: "/import-account",
name: "import-account",
component: () =>
import(
/* webpackChunkName: "import-account" */ "../views/ImportAccountView.vue"
),
component: () => import("../views/ImportAccountView.vue"),
},
{
path: "/import-derive",
name: "import-derive",
component: () =>
import(
/* webpackChunkName: "import-derive" */ "../views/ImportDerivedAccountView.vue"
),
component: () => import("../views/ImportDerivedAccountView.vue"),
},
{
path: "/new-edit-account",
name: "new-edit-account",
component: () =>
import(
/* webpackChunkName: "new-edit-account" */ "../views/NewEditAccountView.vue"
),
component: () => import("../views/NewEditAccountView.vue"),
},
{
path: "/new-edit-project",
name: "new-edit-project",
component: () =>
import(
/* webpackChunkName: "new-edit-project" */ "../views/NewEditProjectView.vue"
),
component: () => import("../views/NewEditProjectView.vue"),
},
{
path: "/new-identifier",
name: "new-identifier",
component: () =>
import(
/* webpackChunkName: "new-identifier" */ "../views/NewIdentifierView.vue"
),
component: () => import("../views/NewIdentifierView.vue"),
},
{
path: "/project/:id?",
name: "project",
component: () =>
import(/* webpackChunkName: "project" */ "../views/ProjectViewView.vue"),
component: () => import("../views/ProjectViewView.vue"),
},
{
path: "/projects",
name: "projects",
component: () =>
import(/* webpackChunkName: "projects" */ "../views/ProjectsView.vue"),
component: () => import("../views/ProjectsView.vue"),
beforeEnter: enterOrStart,
},
{
path: "/quick-action-bvc",
name: "quick-action-bvc",
component: () =>
import(
/* webpackChunkName: "quick-action-bvc" */ "../views/QuickActionBvcView.vue"
),
component: () => import("../views/QuickActionBvcView.vue"),
},
{
path: "/quick-action-bvc-begin",
name: "quick-action-bvc-begin",
component: () =>
import(
/* webpackChunkName: "quick-action-bvc-begin" */ "../views/QuickActionBvcBeginView.vue"
),
component: () => import("../views/QuickActionBvcBeginView.vue"),
},
{
path: "/quick-action-bvc-end",
name: "quick-action-bvc-end",
component: () =>
import(
/* webpackChunkName: "quick-action-bvc-end" */ "../views/QuickActionBvcEndView.vue"
),
component: () => import("../views/QuickActionBvcEndView.vue"),
},
{
path: "/scan-contact",
name: "scan-contact",
component: () =>
import(
/* webpackChunkName: "scan-contact" */ "../views/ContactScanView.vue"
),
component: () => import("../views/ContactScanView.vue"),
},
{
path: "/search-area",
name: "search-area",
component: () =>
import(
/* webpackChunkName: "search-area" */ "../views/SearchAreaView.vue"
),
component: () => import("../views/SearchAreaView.vue"),
},
{
path: "/seed-backup",
name: "seed-backup",
component: () =>
import(
/* webpackChunkName: "seed-backup" */ "../views/SeedBackupView.vue"
),
component: () => import("../views/SeedBackupView.vue"),
},
{
path: "/start",
name: "start",
component: () =>
import(/* webpackChunkName: "start" */ "../views/StartView.vue"),
component: () => import("../views/StartView.vue"),
},
{
path: "/statistics",
name: "statistics",
component: () =>
import(
/* webpackChunkName: "statistics" */ "../views/StatisticsView.vue"
),
component: () => import("../views/StatisticsView.vue"),
},
{
path: "/test",
name: "test",
component: () =>
import(/* webpackChunkName: "test" */ "../views/TestView.vue"),
component: () => import("../views/TestView.vue"),
},
];
/** @type {*} */
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});

1
src/util.d.ts vendored
View File

@@ -1,4 +1,5 @@
// from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/util.d.ts
/* eslint-disable */
/**
* The `node:util` module supports the needs of Node.js internal APIs. Many of the
* utilities are useful for application and module developers as well. To access

View File

@@ -37,7 +37,7 @@
<span>
<router-link
:to="{ name: 'help' }"
class="text-xs uppercase bg-blue-500 text-white px-1.5 py-1 rounded-md ml-1"
class="text-xs uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md ml-1"
>
Help
</router-link>
@@ -55,7 +55,7 @@
</p>
<router-link
:to="{ name: 'start' }"
class="inline-block text-md uppercase bg-amber-600 text-white px-4 py-2 rounded-md"
class="inline-block text-md uppercase bg-gradient-to-b from-amber-400 to-amber-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
>
Create An Identifier
</router-link>
@@ -105,13 +105,15 @@
</p>
<router-link
:to="{ name: 'contact-qr' }"
class="inline-block text-md uppercase bg-amber-600 text-white px-4 py-2 rounded-md"
class="inline-block text-md uppercase bg-gradient-to-b from-amber-400 to-amber-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
>
Share Your Info
</router-link>
</div>
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
<!-- label -->
<div class="mb-2 font-bold">Settings</div>
<div
v-if="!notificationMaybeChanged"
class="flex items-center justify-between cursor-pointer"
@@ -140,16 +142,38 @@
Notification status may have changed. Refresh this page to see the
latest setting.
</div>
<router-link class="px-4 text-sm text-blue-500" to="/help-notifications">
<router-link class="pl-4 text-sm text-blue-500" to="/help-notifications">
Troubleshoot your notification setup.
</router-link>
<router-link
:to="{ name: 'search-area' }"
v-if="activeDid"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-6"
>
Set Search Area…
<!-- If already set, change button label to "Change Search Area" -->
</router-link>
<div class="text-slate-500 text-sm font-bold mt-6 mb-2">
Topics of Interest
</div>
<textarea
class="block w-full rounded border border-slate-400 px-3 py-2"
rows="3"
value="longing, rusted, seventeen, daybreak, furnace, nine, benign, homecoming, one, freight car"
>
</textarea>
<div class="text-slate-500 text-sm mt-2 mb-2">
Separate topics with a comma.
</div>
</div>
<div
v-if="activeDid"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
>
<div class="mb-2">Usage Limits</div>
<div class="mb-2 font-bold">Usage Limits</div>
<!-- show spinner if loading limits -->
<div v-if="loadingLimits" class="text-center">
Checking&hellip; <fa icon="spinner" class="fa-spin"></fa>
@@ -192,7 +216,7 @@
</p>
</div>
<button
class="block float-right w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mt-2"
class="block float-right w-fit text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mt-2"
@click="checkLimits()"
>
Recheck Limits
@@ -200,18 +224,18 @@
</div>
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
<div>Data Export</div>
<div class="mb-2 font-bold">Data Export</div>
<router-link
:to="{ name: 'seed-backup' }"
v-if="activeDid"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2 mt-2"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-2"
>
Backup Identifier Seed
</router-link>
<button
v-bind:class="computedStartDownloadLinkClassNames()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="exportDatabase()"
>
Download Settings & Contacts
@@ -221,7 +245,7 @@
<a
ref="downloadLink"
v-bind:class="computedDownloadLinkClassNames()"
class="block w-full text-center text-md uppercase bg-green-600 text-white px-1.5 py-2 rounded-md"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
>
If no download happened yet, click again here to download now.
</a>
@@ -302,7 +326,7 @@
<router-link
id="switch-identity-link"
:to="{ name: 'identity-switcher' }"
class="block w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
class="block w-fit text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
>
Switch Identifier
</router-link>
@@ -450,9 +474,15 @@
{{ DEFAULT_PUSH_SERVER }}
</span>
<div class="mt-2">
<span class="text-slate-500 text-sm font-bold">Image Server URL</span>
&nbsp;
<span class="text-sm">{{ DEFAULT_IMAGE_API_SERVER }}</span>
</div>
<label
for="toggleShowShortcutBvc"
class="flex items-center justify-between cursor-pointer my-4"
class="flex items-center justify-between cursor-pointer mt-4"
@click="toggleShowShortcutBvc"
>
<!-- label -->
@@ -482,7 +512,7 @@
<input type="file" @change="uploadFile" class="ml-2" />
<div v-if="showContactImport()">
<button
class="block text-center text-md uppercase bg-blue-500 text-white px-1.5 py-2 rounded-md mb-6"
class="block text-center text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
@click="submitFile()"
>
Import Settings & Contacts
@@ -497,7 +527,7 @@
<button>
<router-link
:to="{ name: 'statistics' }"
class="block w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
class="block w-fit text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
>
See Global Animated History of Giving
</router-link>
@@ -533,9 +563,7 @@ import {
EndorserRateLimits,
ImageRateLimits,
} from "@/libs/endorserServer";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
import { Buffer } from "buffer/";
interface IAccount {
did: string;
@@ -546,12 +574,15 @@ interface IAccount {
const inputFileNameRef = ref<Blob>();
@Component({ components: { QuickNav, TopMessage } })
@Component({
components: { QuickNav, TopMessage },
})
export default class AccountViewView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
AppConstants = AppString;
DEFAULT_PUSH_SERVER = DEFAULT_PUSH_SERVER;
DEFAULT_IMAGE_API_SERVER = DEFAULT_IMAGE_API_SERVER;
activeDid = "";
apiServer = "";
@@ -943,13 +974,13 @@ export default class AccountViewView extends Vue {
public computedStartDownloadLinkClassNames() {
return {
invisible: this.downloadUrl,
hidden: this.downloadUrl,
};
}
public computedDownloadLinkClassNames() {
return {
invisible: !this.downloadUrl,
hidden: !this.downloadUrl,
};
}
@@ -1137,9 +1168,9 @@ export default class AccountViewView extends Vue {
this.limitsMessage =
(data?.error?.message as string) || "Bad server response.";
console.error(
"Got bad response retrieving limits, which usually means user isn't registered:",
error,
"Got bad response retrieving limits, which usually means user isn't registered.",
);
//console.error(error);
} else {
this.limitsMessage = "Got an error retrieving limits.";
console.error("Got some error retrieving limits:", error);

View File

@@ -123,7 +123,7 @@
<div class="columns-3">
<button
class="col-span-1 bg-blue-600 text-white px-4 py-2 rounded-md"
class="col-span-1 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
v-if="
libsUtil.isGiveRecordTheUserCanConfirm(
veriClaim,
@@ -140,7 +140,7 @@
<button
v-if="libsUtil.canFulfillOffer(veriClaim)"
@click="openFulfillGiftDialog()"
class="col-span-1 block w-fit text-center text-md bg-blue-600 text-white px-1.5 py-2 rounded-md"
class="col-span-1 block w-fit text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
>
Affirm Delivery
<fa icon="hand-holding-heart" class="ml-2 text-white cursor-pointer" />
@@ -377,7 +377,7 @@
</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"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2"
@click="showFullClaim(veriClaim.id as string)"
>
Load Full Claim Details
@@ -390,7 +390,7 @@
<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"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2"
>
View on the Public Server
</a>

View File

@@ -30,17 +30,19 @@
</div>
<div class="mt-8">
<input
type="submit"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
value="Add Contact"
/>
<button
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<input
type="submit"
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
value="Add Contact"
/>
<button
type="button"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
</div>
</div>
</section>
</template>

View File

@@ -119,7 +119,7 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken, SimpleSigner } from "@/libs/crypto";
import {
AgreeVerifiableCredential,
GiveServerRecord,
GiveSummaryRecord,
GiveVerifiableCredential,
SCHEMA_ORG_CONTEXT,
} from "@/libs/endorserServer";
@@ -131,7 +131,7 @@ export default class ContactAmountssView extends Vue {
activeDid = "";
apiServer = "";
contact: Contact | null = null;
giveRecords: Array<GiveServerRecord> = [];
giveRecords: Array<GiveSummaryRecord> = [];
numAccounts = 0;
async beforeCreate() {
@@ -197,7 +197,7 @@ export default class ContactAmountssView extends Vue {
async loadGives(activeDid: string, contact: Contact) {
try {
const identity = await this.getIdentity(this.activeDid);
let result: Array<GiveServerRecord> = [];
let result: Array<GiveSummaryRecord> = [];
const url =
this.apiServer +
"/api/v2/report/gives?agentDid=" +
@@ -252,7 +252,7 @@ export default class ContactAmountssView extends Vue {
);
}
const sortedResult: Array<GiveServerRecord> = R.sort(
const sortedResult: Array<GiveSummaryRecord> = R.sort(
(a, b) =>
new Date(b.issuedAt).getTime() - new Date(a.issuedAt).getTime(),
result,
@@ -271,7 +271,7 @@ export default class ContactAmountssView extends Vue {
}
}
async confirm(record: GiveServerRecord) {
async confirm(record: GiveSummaryRecord) {
// Make claim
// I use clone here because otherwise it gets a Proxy object.
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@@ -32,7 +32,7 @@
<button
type="button"
@click="openDialog()"
class="block w-full text-center text-sm uppercase bg-blue-600 text-white px-3 py-1.5 rounded-md"
class="block w-full text-center text-sm uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
>
<fa icon="gift" class="fa-fw"></fa>
</button>
@@ -57,7 +57,7 @@
<button
type="button"
@click="openDialog(contact)"
class="block w-full text-center text-sm uppercase bg-blue-600 text-white px-3 py-1.5 rounded-md"
class="block w-full text-center text-sm uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
>
<fa icon="gift" class="fa-fw"></fa>
</button>

View File

@@ -26,7 +26,7 @@
You aren't sharing your name, so quickly
<router-link
:to="{ name: 'new-edit-account' }"
class="bg-blue-500 text-white px-1.5 py-1 rounded-md"
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md"
>
click here to set it for them.
</router-link>
@@ -88,9 +88,7 @@ import {
CONTACT_URL_PREFIX,
ENDORSER_JWT_URL_LOCATION,
} from "@/libs/endorserServer";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
import { Buffer } from "buffer/";
@Component({
components: {

View File

@@ -65,17 +65,19 @@
/>
<div class="mt-8">
<input
type="submit"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
value="Look Up Contact"
/>
<button
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<input
type="submit"
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
value="Look Up Contact"
/>
<button
type="button"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
</div>
</div>
</section>
</template>

View File

@@ -10,8 +10,9 @@
<span />
<span>
<a
@click="showHintsForOnboarding()"
class="text-xs uppercase bg-blue-500 text-white px-1.5 py-1 rounded-md ml-1"
href="/help-onboarding"
target="_blank"
class="text-xs uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md ml-1"
>
Onboarding Guide
</a>
@@ -22,7 +23,7 @@
<div class="mt-4 mb-4 flex items-stretch">
<router-link
:to="{ name: 'contact-qr' }"
class="flex items-center bg-slate-500 text-white px-1.5 py-1 mr-1 rounded-md"
class="flex items-center bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
>
<fa icon="qrcode" class="fa-fw text-2xl" />
</router-link>
@@ -75,7 +76,7 @@
<br />
(Only most recent hours included. To see more, click
<span
class="text-sm uppercase bg-slate-500 text-white px-1 py-1 rounded-md"
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 py-1 rounded-md"
>
<fa icon="file-lines" class="fa-fw" />
</span>
@@ -100,7 +101,7 @@
></EntityIcon>
{{ contact.name || AppString.NO_CONTACT_NAME }}
<button
class="text-sm uppercase bg-slate-500 text-white px-1 rounded-md"
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 rounded-md"
@click="
contactEdit = contact;
contactNewName = contact.name;
@@ -137,7 +138,7 @@
<div v-if="activeDid">
<button
v-if="contact.seesMe"
class="text-sm uppercase bg-slate-500 text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="setVisibility(contact, false, true)"
title="They can see you"
>
@@ -145,14 +146,14 @@
</button>
<button
v-else
class="text-sm uppercase bg-slate-500 text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="setVisibility(contact, true, true)"
title="They cannot see you"
>
<fa icon="eye-slash" class="fa-fw" />
</button>
<button
class="text-sm uppercase bg-slate-500 text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="checkVisibility(contact)"
title="Check Visibility"
v-if="activeDid"
@@ -161,7 +162,7 @@
</button>
<button
@click="register(contact)"
class="text-sm uppercase bg-slate-500 text-white ml-6 px-2 py-1.5 rounded-md"
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 px-2 py-1.5 rounded-md"
v-if="activeDid"
title="Registration"
>
@@ -176,7 +177,7 @@
<button
@click="deleteContact(contact)"
class="text-sm uppercase bg-red-600 text-white ml-24 px-2 py-1.5 rounded-md"
class="text-sm uppercase bg-gradient-to-b from-rose-500 to-rose-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-24 px-2 py-1.5 rounded-md"
title="Delete"
>
<fa icon="trash-can" class="fa-fw" />
@@ -187,7 +188,7 @@
class="ml-auto flex gap-1.5"
>
<button
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-l-md"
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-l-md"
@click="onClickAddGive(activeDid, contact.did)"
:title="givenByMeDescriptions[contact.did] || ''"
>
@@ -229,7 +230,7 @@
name: 'contact-amounts',
query: { contactDid: contact.did },
}"
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md"
title="See more given activity"
>
<fa icon="file-lines" class="fa-fw" />
@@ -302,7 +303,7 @@ import {
import {
CONTACT_CSV_HEADER,
CONTACT_URL_PREFIX,
GiveServerRecord,
GiveSummaryRecord,
GiveVerifiableCredential,
isDid,
RegisterVerifiableCredential,
@@ -313,8 +314,7 @@ import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import { Account } from "@/db/tables/accounts";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
import { Buffer } from "buffer/";
@Component({
components: { QuickNav, EntityIcon },
@@ -409,7 +409,7 @@ export default class ContactsView extends Vue {
}
const handleResponse = (
resp: { status: number; data: { data: GiveServerRecord[] } },
resp: { status: number; data: { data: GiveSummaryRecord[] } },
descriptions: Record<string, string>,
confirmed: Record<string, number>,
unconfirmed: Record<string, number>,
@@ -510,18 +510,6 @@ export default class ContactsView extends Vue {
}
}
showHintsForOnboarding() {
this.$notify(
{
group: "alert",
type: "info",
title: "Onboard Someone",
text: libsUtil.ONBOARD_MESSAGE,
},
-1,
);
}
async onClickNewContact(): Promise<void> {
if (!this.contactInput) {
this.$notify(
@@ -531,7 +519,7 @@ export default class ContactsView extends Vue {
title: "No Contact",
text: "There was no contact info to add.",
},
-1,
3000,
);
return;
}
@@ -559,7 +547,7 @@ export default class ContactsView extends Vue {
title: "Contacts Added",
text: "Each contact was added. Nothing was sent to the server.",
},
-1, // keeping it up so that the "visibility" message is seen
3000, // keeping it up so that the "visibility" message is seen
);
} catch (e) {
this.$notify(
@@ -664,7 +652,7 @@ export default class ContactsView extends Vue {
title: "No Contact Info",
text: "The contact info could not be parsed.",
},
-1,
3000,
);
return;
} else {
@@ -686,7 +674,7 @@ export default class ContactsView extends Vue {
title: "Incomplete Contact",
text: "Cannot add a contact without a DID.",
},
-1,
5000,
);
return;
}
@@ -698,7 +686,7 @@ export default class ContactsView extends Vue {
title: "Invalid DID",
text: "The DID is not valid. It must begin with 'did:'",
},
-1,
5000,
);
return;
}
@@ -737,7 +725,7 @@ export default class ContactsView extends Vue {
title: "Contact Added",
text: addedMessage,
},
-1, // keeping it up so that the "visibility" message is seen
3000,
);
})
.catch((err) => {
@@ -853,7 +841,7 @@ export default class ContactsView extends Vue {
title: "Registration Still Unknown",
text: message,
},
-1,
5000,
);
} else if (resp.data?.success?.handleId) {
contact.registered = true;
@@ -892,7 +880,7 @@ export default class ContactsView extends Vue {
title: "Registration Error",
text: userMessage,
},
-1,
5000,
);
}
}
@@ -933,7 +921,7 @@ export default class ContactsView extends Vue {
(visibility ? "" : "not ") +
"see your activity.",
},
-1,
3000,
);
}
contact.seesMe = visibility;
@@ -953,7 +941,7 @@ export default class ContactsView extends Vue {
title: "Error Setting Visibility",
text: message,
},
-1,
5000,
);
}
} catch (err) {
@@ -965,7 +953,7 @@ export default class ContactsView extends Vue {
title: "Error Setting Visibility",
text: "Check connectivity and try again.",
},
-1,
5000,
);
}
}
@@ -997,7 +985,7 @@ export default class ContactsView extends Vue {
(visibility ? "" : "not ") +
"see your activity.",
},
-1,
3000,
);
} else {
console.error("Got bad server response checking visibility:", resp);
@@ -1009,7 +997,7 @@ export default class ContactsView extends Vue {
title: "Error Checking Visibility",
text: message,
},
-1,
5000,
);
}
} catch (err) {
@@ -1021,7 +1009,7 @@ export default class ContactsView extends Vue {
title: "Error Checking Visibility",
text: "Check connectivity and try again.",
},
-1,
3000,
);
}
}
@@ -1069,7 +1057,7 @@ export default class ContactsView extends Vue {
title: "Input Error",
text: "This is not a valid number of hours: " + this.hourInput,
},
-1,
3000,
);
} else if (parseFloat(this.hourInput) == 0 && !this.hourDescriptionInput) {
this.$notify(
@@ -1079,7 +1067,7 @@ export default class ContactsView extends Vue {
title: "Input Error",
text: "Giving no hours or description does nothing.",
},
-1,
3000,
);
} else if (!identity) {
this.$notify(
@@ -1089,7 +1077,7 @@ export default class ContactsView extends Vue {
title: "Status Error",
text: "No identifier is available.",
},
-1,
3000,
);
} else {
// ask to confirm amount
@@ -1218,7 +1206,7 @@ export default class ContactsView extends Vue {
title: "Error Sending Give",
text: userMessage,
},
-1,
5000,
);
}
}

View File

@@ -65,7 +65,7 @@
<span v-else>
<fa
icon="camera"
class="bg-blue-500 text-white px-2 py-2 rounded-md"
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
@click="openPhotoDialog"
/>
</span>
@@ -92,19 +92,26 @@
<p class="text-center mb-2 mt-6 italic">
Sign & Send to publish to the world
<fa
icon="circle-info"
class="pl-2 text-blue-500 cursor-pointer"
@click="explainData()"
/>
</p>
<button
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
@click="confirm"
>
Sign &amp; Send
</button>
<button
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
@click="cancel"
>
Cancel
</button>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
@click="confirm"
>
Sign &amp; Send
</button>
<button
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="cancel"
>
Cancel
</button>
</div>
</section>
</template>
@@ -288,6 +295,45 @@ export default class GiftedDetails extends Vue {
}
async confirm() {
if (!this.activeDid) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "You must select an identifier before you can record a give.",
},
2000,
);
return;
}
if (parseFloat(this.amountInput) < 0) {
this.$notify(
{
group: "alert",
type: "danger",
text: "You may not send a negative number.",
title: "",
},
2000,
);
return;
}
if (!this.description && !parseFloat(this.amountInput)) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: `You must enter a description or some number of ${
this.libsUtil.UNIT_LONG[this.unitCode]
}.`,
},
2000,
);
return;
}
this.$notify(
{
group: "alert",
@@ -297,6 +343,7 @@ export default class GiftedDetails extends Vue {
},
1000,
);
// this is asynchronous, but we don't need to wait for it to complete
await this.recordGive();
}
@@ -309,34 +356,6 @@ export default class GiftedDetails extends Vue {
* @param unitCode may be omitted, defaults to "HUR"
*/
public async recordGive() {
if (!this.activeDid) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "You must select an identifier before you can record a give.",
},
-1,
);
return;
}
if (!this.description && !this.amountInput) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: `You must enter a description or some number of ${
this.libsUtil.UNIT_LONG[this.unitCode]
}.`,
},
-1,
);
return;
}
try {
const identity = await libsUtil.getIdentity(this.activeDid);
const result = await createAndSubmitGive(
@@ -424,5 +443,17 @@ export default class GiftedDetails extends Vue {
result.response?.data?.error?.message
);
}
explainData() {
this.$notify(
{
group: "alert",
type: "success",
title: "Data Sharing",
text: libsUtil.PRIVACY_MESSAGE,
},
-1,
);
}
}
</script>

View File

@@ -31,7 +31,7 @@
If this works then you're all set.
<button
@click="sendTestWebPushMessage(true)"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2"
>
Send Yourself a Test Web Push Message (Through Push Server but
Skipping Client Filter)
@@ -233,7 +233,7 @@
<h2 class="text-xl font-semibold mt-4">Tests</h2>
<button
@click="showTestNotification()"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4 mb-2"
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
>
Send Test Notification Directly to Device (Not Through Push Server)
</button>
@@ -246,7 +246,7 @@
<button
@click="alertWebPushSubscription()"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4 mb-2"
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
>
Show Web Push Subscription Info
</button>
@@ -259,7 +259,7 @@
<button
@click="sendTestWebPushMessage(true)"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4 mb-2"
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
>
Send Yourself a Test Web Push Message (Through Push Server but Skipping
Client Filter)
@@ -272,7 +272,7 @@
<button
@click="sendTestWebPushMessage()"
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4 mb-2"
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
>
Send Yourself a Test Web Push Message (Through Push Server and Client
Filter)
@@ -301,12 +301,13 @@ import { sendTestThroughPushServer } from "@/libs/util";
export default class HelpNotificationsView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
subscription: PushSubscription | null = null;
subscriptionJSON?: PushSubscriptionJSON;
async mounted() {
try {
const registration = await navigator.serviceWorker.ready;
this.subscription = await registration.pushManager.getSubscription();
const fullSub = await registration.pushManager.getSubscription();
this.subscriptionJSON = fullSub?.toJSON();
} catch (error) {
console.error("Mount error:", error);
}
@@ -315,13 +316,13 @@ export default class HelpNotificationsView extends Vue {
alertWebPushSubscription() {
console.log(
"Web push subscription:",
JSON.parse(JSON.stringify(this.subscription)), // gives more info than plain console logging
JSON.parse(JSON.stringify(this.subscriptionJSON)), // gives more info than plain console logging
);
alert(JSON.stringify(this.subscription));
alert(JSON.stringify(this.subscriptionJSON));
}
async sendTestWebPushMessage(skipFilter: boolean = false) {
if (!this.subscription) {
if (!this.subscriptionJSON) {
this.$notify(
{
group: "alert",
@@ -336,7 +337,7 @@ export default class HelpNotificationsView extends Vue {
}
try {
await sendTestThroughPushServer(this.subscription, skipFilter);
await sendTestThroughPushServer(this.subscriptionJSON, skipFilter);
this.$notify(
{

View File

@@ -0,0 +1,69 @@
<template>
<!-- Don't include nav buttons since this is shown in a different window. -->
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div class="mb-8">
<!-- Don't include 'back' button since this is shown in a different window. -->
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Time Safari Onboarding Instructions
</h1>
</div>
<!-- eslint-disable prettier/prettier -->
<div class="ml-4">
<h1 class="font-bold text-xl">Install</h1>
<div>
<p>
1) Have them visit TimeSafari.app in a browser, preferably Chrome or Safari.
</p>
<p>
2) Have them "Install" the site to their desktop.
</p>
</div>
<h1 class="font-bold text-xl">Add Contact & Register</h1>
<div>
<p>
3) Have them follow their yellow prompts.
</p>
<p>
4) Add them to your contacts <fa icon="users" />
</p>
<p>
5) Register them <fa icon="person-circle-question" />
</p>
<p>
6) Add yourself to their contacts <fa icon="users" />
</p>
</div>
<h1 class="font-bold text-xl">Enable Notifications</h1>
<div>
<p>
7) Enable notifications from <fa icon="circle-user" />
</p>
</div>
<h1 class="font-bold text-xl">Discuss Backups</h1>
<div>
<p>
8) Exporting backups <fa icon="circle-user" /> are important if they lose their phone --- especially for the Identifier Seed!
</p>
</div>
</div>
<!-- eslint enable -->
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import QuickNav from "@/components/QuickNav.vue";
@Component({ components: { QuickNav } })
export default class Help extends Vue {}
</script>

View File

@@ -11,7 +11,7 @@
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
@click="$router.back()"
>
<fa icon="chevron-left" class="fa-fw"></fa>
<fa icon="chevron-left" class="fa-fw" />
</h1>
</div>
@@ -39,22 +39,22 @@
and network.
</p>
<p>
You can show giving and also offer help to ideas, based on others'
willingness to help out, too. You can record your own ideas and invite
others to collaborate.
You highlight giving and also offer help to ideas -- which could be
conditional on others' willingness to help, too.
You can record your own ideas and invite others to collaborate.
</p>
<p>
This app uses the power of cryptography to build a reputation, recording
activity that you can share at your discretion. You put some activity
public, but your sensitive information is not shared with anyone,
including our services. This is in contrast to Meta and Google, who hold
your data and allow you use it. Those services are useful, but they have
the control; this app gives you the control.
public, but these services don't share your ID with others without explicit consent.
This is in contrast to Meta and Google, who hold
your data and allow you use it while they manage sharing...
those services are useful but they have the control, whereas this app gives you the control.
</p>
<h2 class="text-xl font-semibold">How do I get started?</h2>
<p>
You need someone to register you -- usually the person who told you
You need someone to register you, like the person who told you
about this app, on the Contacts
<fa icon="users" class="fa-fw" /> page. After they register you, you can
select any contact on the home page (or "anonymous") and record your
@@ -83,9 +83,9 @@
<h2 class="text-xl font-semibold">How do I add someone else?</h2>
<p>
<button class="text-blue-500" @click="showOnboardInfo">
<a href="/help-onboarding" target="_blank" class="text-blue-500">
Click here to show an alert with the steps.
</button>
</a>
To start scanning, go
<router-link class="text-blue-500" to="/contact-qr">here.</router-link>
</p>
@@ -198,9 +198,7 @@
<ul>
<li class="list-disc list-outside ml-4">
Chrome:
<a href="chrome://settings/content/all" class="text-blue-500"
>clear here</a
>
Clear at chrome://settings/content/all and
also clear under dev tools Application
</li>
<li class="list-disc list-outside ml-4">
@@ -217,6 +215,28 @@
</ul>
<p>To erase your data from our servers, contact us (below).</p>
<h2 class="text-xl font-semibold">
How do I get higher limits?
</h2>
<p>
Let's talk. Contact us (below).
</p>
<h2 class="text-xl font-semibold">
How do I access even more functionality?
</h2>
<p>
There is an "Advanced" section at the bottom of the Account
<fa icon="circle-user" /> page.
</p>
<p>
There is a even more functionality in a mobile app (and more
documentation) at
<a href="https://endorser.ch" class="text-blue-500">
EndorserSearch.com
</a>
</p>
<h2 class="text-xl font-semibold">
I know there is a record from someone, so why can't I see that info?
</h2>
@@ -240,35 +260,65 @@
</h2>
<p>
<router-link class="text-blue-500" to="/help-notifications"
>Here.</router-link
>Here.</router-link
>
</p>
<h2 class="text-xl font-semibold">
How do I get higher limits?
My app is misbehaving, like showing me a blank screen or failing to show a feed.
What can I do?
</h2>
<p>
Let's talk. Contact us (below).
First, note that clearing the cache will clear all your identity and contact info,
so we recommend doing other things first (unless you know you have your backups ready).
</p>
<h2 class="text-xl font-semibold">
How do I access even more functionality?
</h2>
<ul class="list-disc list-outside ml-4">
<li>
Drag down on the screen to refresh it; do that multiple times, because
it sometimes takes multiple tries for the app to refresh to the current version.
You can see the version information at the bottom of this page; the best
way to determine the current version is to open this page in an incognito
browser window and look at the version there.
</li>
<li>
Close all tabs that have Time Safari open; it can be difficult to find them all,
and you may have to close all your tabs. In addition, it may be running as an
installed app, so look for any Time Safari app that may be running outside a browser.
</li>
<li>
It can help to reregister the service worker:
<ul>
<li>
In Chrome, open a tab to
"chrome://serviceworker-internals",
find "timesafari.app", and click "Unregister".</li>
<li>
In Firefox,
open a tab to "about:serviceworkers",
find "timesafari.app", and click "Unregister".
</li>
<li>
<a href="https://duckduckgo.com/?q=unregister+service+worker" class="text-blue-500">Search</a>
for instructions for other browsers.</li>
</ul>
Then reload Time Safari.
</li>
<li>
Restart your device.
</li>
</ul>
<p>
There is an "Advanced" section at the bottom of the Account
<fa icon="circle-user" /> page.
</p>
<p>
There is a even more functionality in a mobile app (and more
documentation) at
<a href="https://endorser.ch" class="text-blue-500">
EndorserSearch.com
</a>
If you still have problems, you can clear the cache (see "erase my data" above)
and even uninstall and reinstall the app
-- just be sure to have your backups ready or be
prepared to restart with a new identity and recreate your network.
Nobody else has access to your identity or contact information because
this app is designed to give you full control over your data.
</p>
<h2 class="text-xl font-semibold">What are the terms & conditions and the privacy policy?</h2>
<p style="display:inline; align-items: center">
This work is marked with
This work is public domain, governed by
<a href="http://creativecommons.org/publicdomain/zero/1.0?ref=chooser-v1" target="_blank" rel="license noopener noreferrer">
<span class="text-blue-500 mr-1">CC0 1.0</span>
<img
@@ -326,25 +376,12 @@ import { Component, Vue } from "vue-facing-decorator";
import * as Package from "../../package.json";
import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app";
import { ONBOARD_MESSAGE } from "@/libs/util";
@Component({ components: { QuickNav } })
export default class Help extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
package = Package;
commitHash = process.env.VUE_APP_GIT_HASH;
showOnboardInfo() {
this.$notify(
{
group: "alert",
type: "info",
title: "Onboard Someone",
text: ONBOARD_MESSAGE,
},
-1,
);
}
commitHash = import.meta.env.VITE_GIT_HASH;
}
</script>

View File

@@ -62,7 +62,7 @@
<div v-if="showShortcutBvc" class="mb-4">
<router-link
:to="{ name: 'quick-action-bvc' }"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
class="block text-center text-md font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
>
Bountiful Voluntaryist Community Actions</router-link
>
@@ -85,7 +85,7 @@
</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"
class="block text-center text-md font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
>
Create An Identifier</router-link
>
@@ -99,7 +99,7 @@
giving.
<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"
class="block text-center text-md font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
>
Show Them Your Identifier Info</router-link
>
@@ -118,7 +118,9 @@
<h2 class="text-xl font-bold">Record Something Given By:</h2>
</div>
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
<ul
class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-x-3 gap-y-5 text-center mb-5"
>
<li @click="openDialog()">
<img
src="../assets/blank-square.svg"
@@ -152,13 +154,13 @@
<router-link
v-if="allContacts.length >= 7"
:to="{ name: 'contact-gives' }"
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
class="block text-center text-md font-bold uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
>
Choose From All Contacts
</router-link>
<button
@click="openGiftedPrompts()"
class="block text-center text-md font-bold bg-slate-500 text-white px-2 py-3 rounded-md"
class="block text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
>
Ideas...
</button>
@@ -172,10 +174,29 @@
showGivenToUser="true"
/>
<GiftedPrompts ref="giftedPrompts" />
<FeedFilters ref="feedFilters" />
<!-- Results List -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<h2 class="text-xl font-bold mb-4">Latest Activity</h2>
<div class="flex items-center mb-4">
<h2 class="text-xl font-bold">Latest Activity</h2>
<button @click="openFeedFilters()" class="block text-center ml-auto">
<span class="text-sm uppercase text-white">
<span
v-if="resultsAreFiltered()"
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md"
>
Filtered
</span>
<span
v-else
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md"
>
Unfiltered
</span>
</span>
</button>
</div>
<InfiniteScroll @reached-bottom="loadMoreGives">
<ul class="border-t border-slate-300">
<li
@@ -191,37 +212,36 @@
</div>
<div class="grid grid-cols-12">
<span class="col-span-11 justify-self-start">
<span class="col-span-1 justify-self-start">
<span>
<fa
v-if="record.giver.known || record.receiver.known"
icon="circle-user"
class="col-span-1 pt-1 pl-0 pr-3 text-slate-500"
/>
<fa
v-else
icon="gift"
class="col-span-1 pt-1 pl-3 pr-0 text-slate-500"
class="pt-1 text-slate-500"
/>
<fa v-else icon="gift" class="pt-1 pl-3 text-slate-500" />
</span>
</span>
<span class="col-span-10 justify-self-stretch">
<span class="pl-2">
{{ giveDescription(record) }}
</span>
{{ giveDescription(record) }}
<a @click="onClickLoadClaim(record.jwtId)">
<fa
icon="circle-info"
icon="file-lines"
class="pl-2 text-blue-500 cursor-pointer"
></fa>
</a>
</span>
<span class="col-span-1 justify-self-end shrink">
<span class="col-span-1 justify-self-end">
<router-link
v-if="record.fulfillsPlanHandleId"
:to="
'/project/' +
encodeURIComponent(record.fulfillsPlanHandleId)
"
class="justify-end"
>
<fa icon="hammer" class="ml-4 pl-2 text-blue-500"></fa>
<fa icon="hammer" class="text-blue-500"></fa>
</router-link>
</span>
</div>
@@ -238,11 +258,17 @@
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
</p>
</div>
<div v-if="!isFeedLoading && feedData.length === 0">
<p class="text-slate-500 text-center italic mt-4 mb-4">
No claims match your filters.
</p>
</div>
</div>
</section>
</template>
<script lang="ts">
import * as R from "ramda";
import { UAParser } from "ua-parser-js";
import { IIdentifier } from "@veramo/core";
import { Component, Vue } from "vue-facing-decorator";
@@ -250,6 +276,7 @@ import { Component, Vue } from "vue-facing-decorator";
import EntityIcon from "@/components/EntityIcon.vue";
import GiftedDialog from "@/components/GiftedDialog.vue";
import GiftedPrompts from "@/components/GiftedPrompts.vue";
import FeedFilters from "@/components/FeedFilters.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
@@ -257,22 +284,30 @@ import { NotificationIface } from "@/constants/app";
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 {
BoundingBox,
isAnyFeedFilterOn,
MASTER_SETTINGS_KEY,
Settings,
} from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import {
contactForDid,
containsNonHiddenDid,
didInfoForContact,
getPlanFromCache,
GiverInputInfo,
GiveServerRecord,
GiveSummaryRecord,
} from "@/libs/endorserServer";
import { generateSaveAndActivateIdentity } from "@/libs/util";
interface GiveRecordWithContactInfo extends GiveServerRecord {
interface GiveRecordWithContactInfo extends GiveSummaryRecord {
giver: {
displayName: string;
known: boolean;
};
image: string;
recipientProjectName: string | undefined;
receiver: {
displayName: string;
known: boolean;
@@ -283,6 +318,7 @@ interface GiveRecordWithContactInfo extends GiveServerRecord {
components: {
GiftedDialog,
GiftedPrompts,
FeedFilters,
QuickNav,
EntityIcon,
InfiniteScroll,
@@ -299,9 +335,16 @@ export default class HomeView extends Vue {
feedData: GiveRecordWithContactInfo[] = [];
feedPreviousOldestId?: string;
feedLastViewedClaimId?: string;
isAnyFeedFilterOn: boolean;
isCreatingIdentifier = false;
isFeedFilteredByVisible = false;
isFeedFilteredByNearby = false;
isFeedLoading = true;
isRegistered = false;
searchBoxes: Array<{
name: string;
bbox: BoundingBox;
}> = [];
showShortcutBvc = false;
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
@@ -324,7 +367,7 @@ export default class HomeView extends Vue {
return headers;
}
async created() {
async mounted() {
try {
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
@@ -336,9 +379,14 @@ export default class HomeView extends Vue {
this.activeDid = settings?.activeDid || "";
this.allContacts = await db.contacts.toArray();
this.feedLastViewedClaimId = settings?.lastViewedClaimId;
this.isFeedFilteredByVisible = !!settings?.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
this.isRegistered = !!settings?.isRegistered;
this.searchBoxes = settings?.searchBoxes || [];
this.showShortcutBvc = !!settings?.showShortcutBvc;
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
if (this.allMyDids.length === 0) {
this.isCreatingIdentifier = true;
this.activeDid = await generateSaveAndActivateIdentity();
@@ -367,6 +415,10 @@ export default class HomeView extends Vue {
}
}
resultsAreFiltered() {
return this.isFeedFilteredByVisible || this.isFeedFilteredByNearby;
}
notificationsSupported() {
return "Notification" in window;
}
@@ -376,27 +428,34 @@ export default class HomeView extends Vue {
"Content-Type": "application/json",
};
const identity = await this.getIdentity(this.activeDid);
if (this.activeDid) {
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
const account = allAccounts.find(
(acc) => acc.did === this.activeDid,
) as Account;
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
if (identity) {
headers["Authorization"] = "Bearer " + (await accessToken(identity));
} else {
throw new Error(
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service. Switch your ID.",
);
}
headers["Authorization"] = "Bearer " + (await accessToken(identity));
} else {
// it's OK without auth... we just won't get any identifiers
}
return headers;
}
// only called when a setting was changed
async reloadFeedOnChange() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.isFeedFilteredByVisible = !!settings?.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
this.feedData = [];
this.feedPreviousOldestId = undefined;
this.updateAllFeed();
}
/**
* Data loader used by infinite scroller
* @param payload is the flag from the InfiniteScroll indicating if it should load
@@ -407,14 +466,30 @@ export default class HomeView extends Vue {
}
}
latLongInAnySearchBox(lat: number, long: number) {
for (const boxInfo of this.searchBoxes) {
if (
boxInfo.bbox.westLong <= long &&
long <= boxInfo.bbox.eastLong &&
boxInfo.bbox.minLat <= lat &&
lat <= boxInfo.bbox.maxLat
) {
return true;
}
}
}
public async updateAllFeed() {
this.isFeedLoading = true;
let endOfResults = true;
await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
.then(async (results) => {
if (results.data.length > 0) {
endOfResults = false;
// include the descriptions of the giver and receiver
const newFeedData: GiveRecordWithContactInfo = results.data.map(
(record: GiveServerRecord) => {
const identity = await this.getIdentity(this.activeDid);
const newFeedData: Array<Promise<GiveRecordWithContactInfo>> =
results.data.map(async (record: GiveSummaryRecord) => {
// similar code is in endorser-mobile utility.ts
// claim.claim happen for some claims wrapped in a Verifiable Credential
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -425,6 +500,36 @@ export default class HomeView extends Vue {
// recipient.did is for legacy data, before March 2023
const recipientDid =
claim.recipient?.identifier || (claim.recipient as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
const plan = await getPlanFromCache(
record.fulfillsPlanHandleId,
identity,
this.axios,
this.apiServer,
);
// check if the record should be filtered out
let anyMatch = false;
if (
this.isFeedFilteredByVisible &&
containsNonHiddenDid(record)
) {
// has a visible DID so it's a keeper
anyMatch = true;
}
if (!anyMatch && this.isFeedFilteredByNearby) {
// check if the associated project has a location inside user's search box
if (record.fulfillsPlanHandleId) {
if (plan?.locLat && plan?.locLon) {
if (this.latLongInAnySearchBox(plan.locLat, plan.locLon)) {
anyMatch = true;
}
}
}
}
if (this.isAnyFeedFilterOn && !anyMatch) {
return null;
}
return {
...record,
giver: didInfoForContact(
@@ -434,6 +539,7 @@ export default class HomeView extends Vue {
this.allMyDids,
),
image: claim.image,
recipientProjectName: plan?.name,
receiver: didInfoForContact(
recipientDid,
this.activeDid,
@@ -441,9 +547,11 @@ export default class HomeView extends Vue {
this.allMyDids,
),
};
},
);
this.feedData = this.feedData.concat(newFeedData);
});
const allNewFeedData: GiveRecordWithContactInfo[] =
await Promise.all(newFeedData);
const filteredFeedData = allNewFeedData.filter(R.isNotNil);
this.feedData = this.feedData.concat(filteredFeedData);
this.feedPreviousOldestId =
results.data[results.data.length - 1].jwtId;
// The following update is only done on the first load.
@@ -470,6 +578,10 @@ export default class HomeView extends Vue {
-1,
);
});
if (this.feedData.length === 0 && !endOfResults) {
// repeat until there's at least some data
this.updateAllFeed();
}
this.isFeedLoading = false;
}
@@ -536,12 +648,28 @@ export default class HomeView extends Vue {
return `${giverInfo.displayName} gave to ${recipientInfo.displayName}: ${gaveAmount}`;
} else if (giverInfo.known) {
// giver is named but recipient is not
// show the project name if to one
if (giveRecord.recipientProjectName) {
// retrieve the project name
return `${giverInfo.displayName} gave: ${gaveAmount} (to the project ${giveRecord.recipientProjectName})`;
}
// it's not to a project
return `${giverInfo.displayName} gave: ${gaveAmount} (to ${recipientInfo.displayName})`;
} else if (recipientInfo.known) {
// recipient is named but giver is not
return `${recipientInfo.displayName} received: ${gaveAmount} (from ${giverInfo.displayName})`;
} else {
// neither giver nor recipient are named
// show the project name if to one
if (giveRecord.recipientProjectName) {
// retrieve the project name
return `${gaveAmount} (to the project ${giveRecord.recipientProjectName})`;
}
// it's not to a project
let peopleInfo;
if (giverInfo.displayName === recipientInfo.displayName) {
peopleInfo = `between two who are ${giverInfo.displayName}`;
@@ -574,5 +702,9 @@ export default class HomeView extends Vue {
openGiftedPrompts() {
(this.$refs.giftedPrompts as GiftedPrompts).open();
}
openFeedFilters() {
(this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange);
}
}
</script>

View File

@@ -65,13 +65,13 @@
<router-link
id="start-link"
:to="{ name: 'start' }"
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
class="block text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
>
Add Another Identity&hellip;
</router-link>
<a
href="#"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-8"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-8"
@click="switchAccount('0')"
>
No Identity

View File

@@ -56,19 +56,21 @@
</div>
<div class="mt-8">
<button
@click="fromMnemonic()"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
>
Import
</button>
<button
@click="onCancelClick()"
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
@click="fromMnemonic()"
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
>
Import
</button>
<button
@click="onCancelClick()"
type="button"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
</div>
</div>
</section>
</template>

View File

@@ -49,19 +49,21 @@
</ul>
</div>
<div class="mt-8">
<button
@click="incrementDerivation()"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
>
Increment and Import
</button>
<button
@click="onCancelClick()"
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
@click="incrementDerivation()"
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
>
Increment and Import
</button>
<button
@click="onCancelClick()"
type="button"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
</div>
</div>
</section>
</template>

View File

@@ -22,21 +22,23 @@
/>
<div class="mt-8">
<button
type="button"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
@click="onClickSaveChanges()"
>
Save Changes
</button>
<!-- SHOW ME instead while processing saving changes -->
<button
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
@click="onClickCancel()"
>
Cancel
</button>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
type="button"
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
@click="onClickSaveChanges()"
>
Save Changes
</button>
<!-- SHOW ME instead while processing saving changes -->
<button
type="button"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="onClickCancel()"
>
Cancel
</button>
</div>
</div>
</section>
</template>

View File

@@ -73,16 +73,17 @@
/>
<label for="includeLocation">Include Location</label>
</div>
<div v-if="includeLocation" style="height: 600px; width: 800px">
<div class="px-2 py-2">
<div v-if="includeLocation" class="mb-4 aspect-video">
<p class="text-sm mb-2 text-slate-500">
For your security, choose a location nearby but not exactly at the
place.
</div>
</p>
<l-map
ref="map"
v-model:zoom="zoom"
:center="[0, 0]"
class="!z-40 rounded-md"
@click="
(event) => {
latitude = event.latlng.lat;
@@ -104,28 +105,30 @@
</div>
<div class="mt-8">
<button
:disabled="isHiddenSave"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
@click="onSaveProjectClick()"
>
<!-- SHOW if in idle state -->
<span :class="{ hidden: isHiddenSave }">Save Project</span>
<!-- SHOW if in saving state; DISABLE button while in saving state -->
<span :class="{ hidden: isHiddenSpinner }">
<!-- icon no worky? -->
<i class="fa-solid fa-spinner fa-spin-pulse"></i>
Saving...</span
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
:disabled="isHiddenSave"
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
@click="onSaveProjectClick()"
>
</button>
<button
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
@click="onCancelClick()"
>
Cancel
</button>
<!-- SHOW if in idle state -->
<span :class="{ hidden: isHiddenSave }">Save Project</span>
<!-- SHOW if in saving state; DISABLE button while in saving state -->
<span :class="{ hidden: isHiddenSpinner }">
<!-- icon no worky? -->
<i class="fa-solid fa-spinner fa-spin-pulse"></i>
Saving...</span
>
</button>
<button
type="button"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="onCancelClick()"
>
Cancel
</button>
</div>
</div>
</section>
</template>
@@ -265,6 +268,8 @@ export default class NewEditProjectView extends Vue {
vcClaim.agent = {
identifier: this.agentDid,
};
} else {
delete vcClaim.agent;
}
if (this.includeLocation) {
vcClaim.location = {
@@ -274,6 +279,8 @@ export default class NewEditProjectView extends Vue {
longitude: this.longitude,
},
};
} else {
delete vcClaim.location;
}
// Make a payload for the claim
const vcPayload = {

View File

@@ -98,14 +98,14 @@
</div>
<a @click="onClickLoadClaim(projectId)" class="cursor-pointer">
<fa icon="circle-info" class="pl-2 pt-1 text-blue-500" />
<fa icon="file-lines" class="pl-2 pt-1 text-blue-500" />
</a>
</div>
<button
v-if="activeDid === issuer || activeDid === agentDid"
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="onEditClick()"
>
Edit
@@ -116,7 +116,7 @@
<div class="text-center">
<button
@click="openOfferDialog()"
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-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
>
Offer (maybe with conditions)...
</button>
@@ -134,7 +134,9 @@
<div class="text-center">
<p class="mt-2 mb-4 text-center">Record a contribution from:</p>
</div>
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
<ul
class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-x-3 gap-y-5 text-center mb-5"
>
<li @click="openGiftDialog({ name: 'you', did: activeDid })">
<fa icon="hand" class="fa-fw text-slate-400 text-5xl" />
<h3
@@ -176,7 +178,7 @@
<a
v-if="allContacts.length >= 7"
@click="onClickAllContactsGifting()"
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
class="block text-center text-md font-bold uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
>
Show More Contacts&hellip;
</a>
@@ -230,7 +232,7 @@
@click="onClickLoadClaim(offer.jwtId as string)"
class="cursor-pointer"
>
<fa icon="circle-info" class="pl-2 pt-1 text-blue-500" />
<fa icon="file-lines" class="pl-2 pt-1 text-blue-500" />
</a>
<a
v-if="checkIsFulfillable(offer)"
@@ -289,7 +291,7 @@
</div>
<div class="flex justify-between">
<a @click="onClickLoadClaim(give.jwtId)">
<fa icon="circle-info" class="text-blue-500 cursor-pointer" />
<fa icon="file-lines" class="text-blue-500 cursor-pointer" />
</a>
<a v-if="checkIsConfirmable(give)" @click="confirmClaim(give)">
<fa icon="circle-check" class="text-blue-500 cursor-pointer" />
@@ -360,11 +362,11 @@ import { accessToken } from "@/libs/crypto";
import * as libsUtil from "@/libs/util";
import {
BLANK_GENERIC_SERVER_RECORD,
GenericServerRecord,
GenericCredWrapper,
GiverInputInfo,
GiveServerRecord,
OfferServerRecord,
PlanServerRecord,
GiveSummaryRecord,
OfferSummaryRecord,
PlanSummaryRecord,
} from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer";
@@ -388,14 +390,14 @@ export default class ProjectViewView extends Vue {
apiServer = "";
description = "";
expanded = false;
fulfilledByThis: PlanServerRecord | null = null;
fulfillersToThis: Array<PlanServerRecord> = [];
givesToThis: Array<GiveServerRecord> = [];
fulfilledByThis: PlanSummaryRecord | null = null;
fulfillersToThis: Array<PlanSummaryRecord> = [];
givesToThis: Array<GiveSummaryRecord> = [];
issuer = "";
latitude = 0;
longitude = 0;
name = "";
offersToThis: Array<OfferServerRecord> = [];
offersToThis: Array<OfferSummaryRecord> = [];
projectId = localStorage.getItem("projectId") || ""; // handle ID
showDidCopy = false;
timeSince = "";
@@ -718,8 +720,8 @@ export default class ProjectViewView extends Vue {
this.$router.push(route);
}
checkIsFulfillable(offer: OfferServerRecord) {
const offerRecord: GenericServerRecord = {
checkIsFulfillable(offer: OfferSummaryRecord) {
const offerRecord: GenericCredWrapper = {
...BLANK_GENERIC_SERVER_RECORD,
claim: offer.fullClaim,
claimType: "Offer",
@@ -728,8 +730,8 @@ export default class ProjectViewView extends Vue {
return libsUtil.canFulfillOffer(offerRecord);
}
onClickFulfillGiveToOffer(offer: OfferServerRecord) {
const offerRecord: GenericServerRecord = {
onClickFulfillGiveToOffer(offer: OfferSummaryRecord) {
const offerRecord: GenericCredWrapper = {
...BLANK_GENERIC_SERVER_RECORD,
claim: offer.fullClaim,
issuer: offer.offeredByDid,
@@ -768,8 +770,8 @@ export default class ProjectViewView extends Vue {
}
}
checkIsConfirmable(give: GiveServerRecord) {
const giveDetails: GenericServerRecord = {
checkIsConfirmable(give: GiveSummaryRecord) {
const giveDetails: GenericCredWrapper = {
...BLANK_GENERIC_SERVER_RECORD,
claim: give.fullClaim,
claimType: "GiveAction",
@@ -779,7 +781,7 @@ export default class ProjectViewView extends Vue {
}
// similar code is found in ClaimView
async confirmClaim(give: GiveServerRecord) {
async confirmClaim(give: GiveSummaryRecord) {
if (confirm("Do you personally confirm that this is true?")) {
// similar logic is found in endorser-mobile
const goodClaim = serverUtil.removeSchemaContext(

View File

@@ -167,7 +167,7 @@
<a @click="onClickLoadClaim(offer.jwtId)">
<fa
icon="circle-info"
icon="file-lines"
class="pl-2 text-blue-500 cursor-pointer"
></fa>
</a>
@@ -223,7 +223,7 @@ import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue";
import ProjectIcon from "@/components/ProjectIcon.vue";
import TopMessage from "@/components/TopMessage.vue";
import { OfferServerRecord, PlanData } from "@/libs/endorserServer";
import { OfferSummaryRecord, PlanData } from "@/libs/endorserServer";
import EntityIcon from "@/components/EntityIcon.vue";
@Component({
@@ -237,7 +237,7 @@ export default class ProjectsView extends Vue {
currentIid: IIdentifier;
isLoading = false;
numAccounts = 0;
offers: OfferServerRecord[] = [];
offers: OfferSummaryRecord[] = [];
showOffers = true;
showProjects = false;

View File

@@ -49,14 +49,14 @@
>
<button
@click="record()"
class="block text-center text-md font-bold bg-blue-500 text-white px-2 py-3 rounded-md w-56"
class="block text-center text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md w-56"
>
Sign & Send
</button>
</div>
<div v-else class="flex justify-center mt-4">
<button
class="block text-center text-md font-bold bg-slate-500 text-white px-2 py-3 rounded-md w-56"
class="block text-center text-md font-bold bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md w-56"
>
Select Your Actions
</button>

View File

@@ -60,7 +60,7 @@
}}
<a @click="onClickLoadClaim(record.id)">
<fa
icon="circle-info"
icon="file-lines"
class="pl-2 text-blue-500 cursor-pointer"
/>
</a>
@@ -109,14 +109,14 @@
>
<button
@click="record()"
class="block text-center text-md font-bold bg-blue-500 text-white px-2 py-3 rounded-md w-56"
class="block text-center text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md w-56"
>
Sign & Send
</button>
</div>
<div v-else class="flex justify-center mt-4">
<button
class="block text-center text-md font-bold bg-slate-500 text-white px-2 py-3 rounded-md w-56"
class="block text-center text-md font-bold bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md w-56"
>
Choose What To Confirm
</button>
@@ -146,7 +146,7 @@ import {
createAndSubmitConfirmation,
createAndSubmitGive,
ErrorResult,
GenericServerRecord,
GenericCredWrapper,
GenericVerifiableCredential,
} from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
@@ -166,7 +166,7 @@ export default class QuickActionBvcBeginView extends Vue {
allMyDids: Array<string> = [];
apiServer = "";
claimCountWithHidden = 0;
claimsToConfirm: GenericServerRecord[] = [];
claimsToConfirm: GenericCredWrapper[] = [];
claimsToConfirmSelected: string[] = [];
description = "breakfast";
loadingConfirms = true;
@@ -228,7 +228,7 @@ export default class QuickActionBvcBeginView extends Vue {
}
await response.json().then((data) => {
const dataByOthers = R.reject(
(claim: GenericServerRecord) => claim.issuer === this.activeDid,
(claim: GenericCredWrapper) => claim.issuer === this.activeDid,
data,
);
const dataByOthersWithoutHidden = R.reject(

View File

@@ -22,13 +22,13 @@
<div>
<router-link
:to="{ name: 'quick-action-bvc-begin' }"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
class="block text-center text-md font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
>
Beginning of Meeting
</router-link>
<router-link
:to="{ name: 'quick-action-bvc-end' }"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
class="block text-center text-md font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
>
End of Meeting
</router-link>

View File

@@ -64,10 +64,11 @@
</div>
</div>
<div style="height: 600px; width: 800px">
<div class="mb-4 aspect-video">
<l-map
ref="map"
:center="[localCenterLat, localCenterLong]"
class="!z-40 rounded-md"
v-model:zoom="localZoom"
@click="setMapPoint"
>
@@ -208,9 +209,9 @@ export default class DiscoverView extends Vue {
group: "alert",
type: "success",
title: "Saved",
text: "That has been saved in your preferences.",
text: "That has been saved in your preferences. You can now filter by it on your home screen feed.",
},
-1,
7000,
);
this.$router.back();
} catch (err) {
@@ -246,6 +247,7 @@ export default class DiscoverView extends Vue {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
searchBoxes: [],
filterFeedByNearby: false,
});
this.searchBox = null;
this.localCenterLat = 0;

View File

@@ -22,7 +22,7 @@
<span>
<router-link
:to="{ name: 'help' }"
class="text-xs uppercase bg-blue-500 text-white px-1.5 py-1 rounded-md ml-1"
class="text-xs uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md ml-1"
>
Help
</router-link>
@@ -48,7 +48,7 @@
<div class="bg-slate-100 rounded-md overflow-hidden p-4 mb-4">
<button
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="showSeedPhrase"
>
Reveal my Seed Phrase

View File

@@ -23,36 +23,40 @@
<!-- id used by puppeteer test script -->
<div id="start-question" class="mt-8">
<p class="text-center text-xl font-light">
Do you want a new identifier of your own?
</p>
<p class="text-center font-light">
If you haven't used this before, click "Yes" to generate a new
identifier.
</p>
<p class="text-center mb-4 font-light">
Only click "No" if you have a seed of 12 or 24 words generated
elsewhere.
</p>
<a
@click="onClickYes()"
class="block w-full text-center text-lg uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
>
Yes, generate one
</a>
<a
@click="onClickNo()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mt-2"
>
No, I have a seed
</a>
<a
v-if="numAccounts > 0"
@click="onClickDerive()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mt-2"
>
Derive new address from existing seed
</a>
<div class="max-w-3xl mx-auto">
<p class="text-center text-xl font-light">
Do you want a new identifier of your own?
</p>
<p class="text-center font-light">
If you haven't used this before, click "Yes" to generate a new
identifier.
</p>
<p class="text-center mb-4 font-light">
Only click "No" if you have a seed of 12 or 24 words generated
elsewhere.
</p>
<a
@click="onClickYes()"
class="block w-full text-center text-lg uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
>
Yes, generate one
</a>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<a
@click="onClickNo()"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
>
No, I have a seed
</a>
<a
v-if="numAccounts > 0"
@click="onClickDerive()"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
>
Derive new address from existing seed
</a>
</div>
</div>
</div>
</section>
</template>

View File

@@ -1,47 +1,35 @@
{
"compilerOptions": {
"allowJs": true,
"resolveJsonModule": true,
"target": "esnext",
"module": "esnext",
"strict": true,
"strictPropertyInitialization": false,
"jsx": "preserve",
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": "./src",
"types": [
"webpack-env"
],
"paths": {
"@/components/*": ["components/*"],
"@/views/*": ["views/*"],
"@/db/*": ["db/*"],
"@/libs/*": ["libs/*"],
"@/constants/*": ["constants/*"],
"@/store/*": ["store/*"],
"compilerOptions": {
"target": "ES2020", // Latest ECMAScript features that are widely supported by modern browsers
"module": "ESNext", // Use ES modules
"strict": true, // Enable all strict type checking options
"jsx": "preserve", // Preserves JSX to be transformed by Babel or another transpiler
"moduleResolution": "node", // Use Node.js style module resolution
"experimentalDecorators": true,
"esModuleInterop": true, // Enables compatibility with CommonJS modules for default imports
"allowSyntheticDefaultImports": true, // Allow default imports from modules with no default export
"forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": "./src", // Base directory to resolve non-relative module names
"paths": {
"@/components/*": ["components/*"],
"@/views/*": ["views/*"],
"@/db/*": ["db/*"],
"@/libs/*": ["libs/*"],
"@/constants/*": ["constants/*"],
"@/store/*": ["store/*"]
},
"lib": ["ES2020", "dom", "dom.iterable"], // Include typings for ES2020 and DOM APIs
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

18
vite.config.mjs Normal file
View File

@@ -0,0 +1,18 @@
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import * as path from "path";
// https://vitejs.dev/config/
export default defineConfig({
server: {
port: 8080
},
plugins: [ vue() ],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
buffer: path.resolve(__dirname, 'node_modules', 'buffer'),
'dexie-export-import/dist/import': 'dexie-export-import/dist/import/index.js',
},
},
});

View File

@@ -2,7 +2,9 @@ const { defineConfig } = require("@vue/cli-service");
const { gitDescribeSync } = require("git-describe");
const { exec } = require("child_process");
process.env.VUE_APP_GIT_HASH = gitDescribeSync().hash;
import.meta.env.VITE_GIT_HASH = gitDescribeSync().hash;
const TIME_SAFARI_APP_TITLE =
import.meta.env.VITE_TIME_SAFARI_APP_TITLE || require("./package.json").name;
module.exports = defineConfig({
transpileDependencies: true,
@@ -30,6 +32,7 @@ module.exports = defineConfig({
],
},
pwa: {
name: TIME_SAFARI_APP_TITLE,
iconPaths: {
faviconSVG: "img/icons/safari-pinned-tab.svg",
},