forked from trent_larson/crowd-funder-for-time-pwa
Compare commits
49 Commits
bvc-shortc
...
035f2a5b04
| Author | SHA1 | Date | |
|---|---|---|---|
| 035f2a5b04 | |||
| 09dccc34d6 | |||
| ca240ab795 | |||
| 01b5ca6ec8 | |||
| 6f49260c1e | |||
| 38f44771e9 | |||
| 7ce00b86e8 | |||
| 5e771e4a24 | |||
| 4dd2c044d5 | |||
| 3bfd54362e | |||
|
|
b6e344a15e | ||
|
|
3d1c46aef8 | ||
| ce05f7d003 | |||
| 313cd79e60 | |||
| 121991b53a | |||
|
|
cbf8cb9f46 | ||
|
|
fe0668e4b3 | ||
| a230506d96 | |||
| c49c55d394 | |||
| ae572afff6 | |||
| ccea2486e4 | |||
| 155343a9d7 | |||
| 85ad295eb9 | |||
| 64322b2804 | |||
| 3e556dfa52 | |||
| 252952e017 | |||
| 251986d2bc | |||
| 49bb1c07b7 | |||
| 67f34f9826 | |||
| 476d35452a | |||
| 26582030df | |||
| ae857f4c8f | |||
| c602c5ce50 | |||
| e4543457e2 | |||
| c58f012d2c | |||
| 792e9cb648 | |||
| acee761906 | |||
| cae2bbc4ff | |||
|
|
a5c3600673 | ||
| 0eb64ed716 | |||
| f1bb1b51aa | |||
| 92b924643e | |||
| ca90447700 | |||
| 750700e75e | |||
| 3612ea4224 | |||
| dbccbf7e4a | |||
| 1258cf02a1 | |||
| a488a36bc0 | |||
| c696de33f3 |
7
.env.development
Normal file
7
.env.development
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
# 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
|
||||
#
|
||||
# ¯\_(ツ)_/¯
|
||||
4
.env.production
Normal file
4
.env.production
Normal file
@@ -0,0 +1,4 @@
|
||||
# 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
|
||||
@@ -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",
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -6,13 +6,34 @@ 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
|
||||
- ?
|
||||
### Changed in DB or environment
|
||||
- Nothing
|
||||
|
||||
|
||||
## [0.2.14] - 2024.02.14
|
||||
## [0.3.4] - 2024.03.21
|
||||
### 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
|
||||
- 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
|
||||
### Added
|
||||
- Shortcut page for Bountiful Voluntaryist Community
|
||||
### Changed
|
||||
- Combine all service worker scripts into a single file
|
||||
- More readable, targeted summaries in home-page feed items
|
||||
### Changed in DB
|
||||
- Nothing
|
||||
|
||||
|
||||
## [0.2.14] - 2024.02.14 - 5f9edea1167dbfb64e16648764eed8c09b24eaeb
|
||||
### Changed
|
||||
- Combine all service worker scripts into a single file.
|
||||
### Changed in DB
|
||||
- Nothing
|
||||
|
||||
|
||||
33
README.md
33
README.md
@@ -18,6 +18,11 @@ npm install
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Builds the production app
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
@@ -32,21 +37,35 @@ npm run lint
|
||||
|
||||
* `npx prettier --write ./sw_scripts/`
|
||||
|
||||
* Update the project.task.yaml & CHANGELOG.md & the version in package.json, run `npm install`, and commit.
|
||||
* Update the project.task.yaml & CHANGELOG.md & the version in package.json, run `npm install`.
|
||||
|
||||
* [Tag wth the new version.](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases)
|
||||
* Record what version is currently on production.
|
||||
|
||||
... though maybe you do that after testing and release, since that isn't used in the build (and you often increment a lot during testing).
|
||||
* Run the correct build
|
||||
|
||||
* If production: change src/constants/app.ts DEFAULT_*_SERVER to be "PROD" and package.json to remove "_Test". Also record what version is on production.
|
||||
* 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.
|
||||
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
|
||||
```
|
||||
|
||||
* `npm run build`
|
||||
* Production
|
||||
```
|
||||
# This picks up values from .env.production
|
||||
npm run build
|
||||
```
|
||||
|
||||
* Get on the server and back up the time-safari folder.
|
||||
* 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), edit package.json to increment version & add "-beta", `npm install`, and commit. Tag if you didn't before. Also record what version is on production.
|
||||
* Revert src/constants/app.ts and package.json (if that was prod).
|
||||
|
||||
* Commit changes. Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
|
||||
|
||||
* [Tag with the new version.](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
};
|
||||
17
index.html
Normal file
17
index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<title>TimeSafari</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but TimeSafari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
15695
package-lock.json
generated
15695
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
107
package.json
107
package.json
@@ -1,94 +1,89 @@
|
||||
{
|
||||
"name": "TimeSafari_Test",
|
||||
"version": "0.2.15-beta",
|
||||
"name": "TimeSafari",
|
||||
"version": "0.3.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
"dev": "vite",
|
||||
"serve": "vite preview",
|
||||
"build": "vite build",
|
||||
"lint": "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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,44 @@
|
||||
|
||||
tasks :
|
||||
|
||||
- bug - landscape doesn't show full camera
|
||||
- bug - got blank screen and errors on iPhone with no bottom tabs
|
||||
- add to readme - check version, close tabs & restart phone if necessary
|
||||
- bug maybe - a new give remembers the previous project
|
||||
- alert & stop if give amount < 0
|
||||
- add warning that all data (except ID) is public
|
||||
- onboarding video
|
||||
- .2 when adding a claim on home screen, push that claim to the top of the list
|
||||
|
||||
- 24 allow a person record with interests, including location; purpose? contact methods? enhance other connections the same? (suggestion from Philippines) assignee-group:ui
|
||||
|
||||
- .1 on feed, don't show "to someone anonymous" if it's to a project
|
||||
- .1 on ideas, put an "x" to close it assignee-group:ui
|
||||
- 16 save data backups in Google
|
||||
- 16 generate and use passkeys for identities
|
||||
|
||||
- .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
|
||||
- .2 fix bottom of project selection map, where the icons are hidden but a tap goes to the icon's page assignee-group:ui
|
||||
- .5 stop from seeing an error on the first page when browser doesn't support service workers (which I've seen on iPhone; visible in Firefox private window)
|
||||
- .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
|
||||
|
||||
- 32 image on give :
|
||||
- Show a camera to take a picture
|
||||
- Scale the image to a reasonable size
|
||||
- Upload to a public readable place
|
||||
- check the rate limits
|
||||
- use CID (hash?)
|
||||
- put the image URL in the claim
|
||||
- Rates - images erased?
|
||||
- image not associated with JWT ULID since that's assigned later
|
||||
- .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
|
||||
|
||||
- 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
|
||||
@@ -37,6 +50,7 @@ tasks :
|
||||
- randomize (not show in order)
|
||||
- checkboxes - show non-person-oriented messages, show only contacts, show only projects
|
||||
|
||||
- .5 add a notice on the front page if their notifications are off
|
||||
- 08 allow user to add a time when they want their daily notification
|
||||
|
||||
- .5 prompt for the name directly when they visit the QR scan page
|
||||
@@ -50,7 +64,7 @@ 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 - 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)
|
||||
@@ -63,16 +77,15 @@ 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
|
||||
|
||||
- .1 update "offer" units to have same functionality as "give" units
|
||||
- .5 add a link to any 'give' records that fulfill an offer on ClaimView
|
||||
- 01 on home page, prompt for install check in addition to "supports notifications" check (since they won't get notified if Chrome is closed)
|
||||
- 01 on Mac (& Windows?) desktop, add a help blurb so that they can find it again (since it doesn't show in Application list)
|
||||
- bug (that is hard to reproduce) - got error adding on Firefox user #0 as contact for themselves
|
||||
@@ -113,6 +126,7 @@ tasks :
|
||||
- .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 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 Move to Vite
|
||||
- 32 accept images for projects
|
||||
|
||||
@@ -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>
|
||||
31
src/App.vue
31
src/App.vue
@@ -148,6 +148,37 @@
|
||||
class="w-full"
|
||||
role="alert"
|
||||
>
|
||||
<div
|
||||
v-if="notification.type === 'confirm'"
|
||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||
>
|
||||
<div
|
||||
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||
>
|
||||
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
||||
<p class="text-lg mb-4">
|
||||
{{ notification.title }}
|
||||
</p>
|
||||
|
||||
<button
|
||||
@click="
|
||||
notification.onYes();
|
||||
close(notification.id);
|
||||
"
|
||||
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
||||
>
|
||||
Yes
|
||||
</button>
|
||||
|
||||
<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"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="notification.type === 'notification-permission'"
|
||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -10,23 +10,22 @@
|
||||
placeholder="What was received"
|
||||
v-model="description"
|
||||
/>
|
||||
<div class="flex flex-row">
|
||||
<div class="flex flex-row justify-center">
|
||||
<span
|
||||
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 w-1/3 text-center text-blue-500 px-2 py-2"
|
||||
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center text-blue-500 px-2 py-2 w-20"
|
||||
@click="changeUnitCode()"
|
||||
>
|
||||
{{ libsUtil.UNIT_SHORT[unitCode] }}
|
||||
</span>
|
||||
<div
|
||||
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
||||
@click="decrement()"
|
||||
v-if="amountInput !== '0'"
|
||||
@click="amountInput === '0' ? null : decrement()"
|
||||
>
|
||||
<fa icon="chevron-left" />
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
class="w-full border border-r-0 border-slate-400 px-2 py-2 text-center"
|
||||
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
||||
v-model="amountInput"
|
||||
/>
|
||||
<div
|
||||
@@ -36,27 +35,39 @@
|
||||
<fa icon="chevron-right" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-right">
|
||||
<span v-if="showGivenToUser" class="mr-16">
|
||||
<input type="checkbox" class="mr-2" v-model="givenToUser" />
|
||||
<label class="text-sm">Given to you</label>
|
||||
</span>
|
||||
<div class="mt-4 flex justify-center">
|
||||
<span>
|
||||
<input type="checkbox" class="mr-2" v-model="isTrade" />
|
||||
<label class="text-sm">Trade (not a gift)</label>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'gifted-details',
|
||||
query: {
|
||||
amountInput,
|
||||
description,
|
||||
giverDid: giver?.did,
|
||||
giverName: giver?.name,
|
||||
message,
|
||||
offerId,
|
||||
projectId,
|
||||
unitCode,
|
||||
},
|
||||
}"
|
||||
class="text-blue-500"
|
||||
>
|
||||
More Options
|
||||
</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-center mb-2 mt-6 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"
|
||||
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="confirm"
|
||||
>
|
||||
Sign & Send
|
||||
</button>
|
||||
<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="cancel"
|
||||
>
|
||||
Cancel
|
||||
@@ -93,9 +104,9 @@ export default class GiftedDialog extends Vue {
|
||||
apiServer = "";
|
||||
|
||||
amountInput = "0";
|
||||
giver?: GiverInputInfo; // undefined means no identified giver agent
|
||||
description = "";
|
||||
givenToUser = false;
|
||||
giver?: GiverInputInfo; // undefined means no identified giver agent
|
||||
isTrade = false;
|
||||
offerId = "";
|
||||
unitCode = "HUR";
|
||||
|
||||
314
src/components/GiftedPhotoDialog.vue
Normal file
314
src/components/GiftedPhotoDialog.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<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="w-[1em]"></fa>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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-center gap-2 absolute bottom-[1rem] left-[1rem] right-[1rem] bg-black/50 px-4 py-2"
|
||||
>
|
||||
<button
|
||||
@click="uploadImage"
|
||||
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-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>
|
||||
<div class="flex justify-center">
|
||||
<img :src="URL.createObjectURL(blob)" class="mt-2 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<!--
|
||||
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 portrait:bottom-0 portrait:left-0 portrait:right-0 landscape:right-0 landscape:top-0 landscape:bottom-0 flex landscape:flex-row justify-center items-center portrait:pb-2 landscape:pr-4"
|
||||
>
|
||||
<button
|
||||
@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="w-[1em]"></fa>
|
||||
</button>
|
||||
</div>
|
||||
</camera>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import axios from "axios";
|
||||
import Camera from "simple-vue-camera";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
|
||||
import { getIdentity } from "@/libs/util";
|
||||
import { db } from "@/db/index";
|
||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
|
||||
@Component({ components: { Camera } })
|
||||
export default class GiftedPhotoDialog extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
|
||||
activeDid = "";
|
||||
blob: Blob | null = null;
|
||||
setImage: (arg: string) => void = () => {};
|
||||
imageHeight?: number = window.innerHeight / 2;
|
||||
imageWidth?: number = window.innerWidth / 2;
|
||||
imageWarning = ".";
|
||||
uploading = false;
|
||||
visible = false;
|
||||
|
||||
URL = window.URL || window.webkitURL;
|
||||
|
||||
async mounted() {
|
||||
try {
|
||||
await db.open();
|
||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
||||
this.activeDid = settings?.activeDid || "";
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
console.error("Error retrieving settings from database:", err);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: err.message || "There was an error retrieving your settings.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 takeImage(/* payload: MouseEvent */) {
|
||||
const cameraComponent = this.$refs.camera as InstanceType<typeof Camera>;
|
||||
|
||||
/**
|
||||
* 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(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "There was an error taking the picture. Please try again.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async retryImage() {
|
||||
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() {
|
||||
console.log("camera_button clicked");
|
||||
const video = document.querySelector("#video");
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: true,
|
||||
audio: false,
|
||||
});
|
||||
if (video instanceof HTMLVideoElement) {
|
||||
video.srcObject = stream;
|
||||
}
|
||||
}
|
||||
photoSnapped() {
|
||||
console.log("snap_photo clicked");
|
||||
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");
|
||||
console.log(image_data_url);
|
||||
}
|
||||
}
|
||||
****/
|
||||
|
||||
async uploadImage() {
|
||||
this.uploading = true;
|
||||
const identifier = await getIdentity(this.activeDid);
|
||||
const token = await accessToken(identifier);
|
||||
const headers = {
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
const formData = new FormData();
|
||||
if (!this.blob) {
|
||||
// yeah, this should never happen, but it helps with subsequent type checking
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "There was an error finding the picture. Please try again.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
this.uploading = false;
|
||||
return;
|
||||
}
|
||||
formData.append("image", this.blob, "snapshot.png"); // png is set in snapshot()
|
||||
formData.append("claimType", "GiveAction");
|
||||
try {
|
||||
const response = await axios.post(
|
||||
DEFAULT_IMAGE_API_SERVER + "/image",
|
||||
formData,
|
||||
{ headers },
|
||||
);
|
||||
this.uploading = false;
|
||||
|
||||
this.visible = false;
|
||||
this.blob = null;
|
||||
this.setImage(response.data.url as string);
|
||||
} catch (error) {
|
||||
console.error("Error uploading the image", error);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "There was an error saving the picture. Please try again.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
this.uploading = false;
|
||||
this.blob = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background-color: white;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
}
|
||||
</style>
|
||||
@@ -33,7 +33,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 +52,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!
|
||||
|
||||
@@ -51,13 +51,13 @@
|
||||
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"
|
||||
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="confirm"
|
||||
>
|
||||
Sign & Send
|
||||
</button>
|
||||
<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="cancel"
|
||||
>
|
||||
Cancel
|
||||
|
||||
@@ -12,10 +12,20 @@ export enum AppString {
|
||||
TEST1_PUSH_SERVER = "https://test.timesafari.app",
|
||||
TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com",
|
||||
|
||||
PROD_IMAGE_API_SERVER = "https://image-api.timesafari.app",
|
||||
TEST_IMAGE_API_SERVER = "https://test-image-api.timesafari.app",
|
||||
LOCAL_IMAGE_API_SERVER = "http://localhost:3001",
|
||||
|
||||
NO_CONTACT_NAME = "(no name)",
|
||||
}
|
||||
|
||||
export const DEFAULT_ENDORSER_API_SERVER = AppString.TEST_ENDORSER_API_SERVER;
|
||||
export const DEFAULT_ENDORSER_API_SERVER =
|
||||
import.meta.env.VITE_DEFAULT_ENDORSER_API_SERVER ||
|
||||
AppString.TEST_ENDORSER_API_SERVER;
|
||||
|
||||
export const DEFAULT_IMAGE_API_SERVER =
|
||||
import.meta.env.VITE_DEFAULT_IMAGE_API_SERVER ||
|
||||
AppString.TEST_IMAGE_API_SERVER;
|
||||
|
||||
export const DEFAULT_PUSH_SERVER =
|
||||
window.location.protocol + "//" + window.location.host;
|
||||
@@ -29,4 +39,5 @@ export interface NotificationIface {
|
||||
type: string; // "toast" | "info" | "success" | "warning" | "danger"
|
||||
title: string;
|
||||
text: string;
|
||||
onYes?: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
* ...
|
||||
* })
|
||||
|
||||
@@ -67,6 +67,7 @@ export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = {
|
||||
issuer: "",
|
||||
};
|
||||
|
||||
// a summary record; the VC is found the fullClaim field
|
||||
export interface GiveServerRecord {
|
||||
agentDid: string;
|
||||
amount: number;
|
||||
@@ -81,6 +82,7 @@ export interface GiveServerRecord {
|
||||
unit: string;
|
||||
}
|
||||
|
||||
// a summary record; the VC is found the fullClaim field
|
||||
export interface OfferServerRecord {
|
||||
amount: number;
|
||||
amountGiven: number;
|
||||
@@ -98,13 +100,14 @@ export interface OfferServerRecord {
|
||||
validThrough: string;
|
||||
}
|
||||
|
||||
// a summary record; the VC is not currently part of this record
|
||||
export interface PlanServerRecord {
|
||||
agentDid?: string; // optional, if the issuer wants someone else to manage as well
|
||||
description: string;
|
||||
endTime?: string;
|
||||
fulfillsPlanHandleId: string;
|
||||
issuerDid: string;
|
||||
handleId: string;
|
||||
issuerDid: string;
|
||||
locLat?: number;
|
||||
locLon?: number;
|
||||
startTime?: string;
|
||||
@@ -120,6 +123,7 @@ export interface GiveVerifiableCredential {
|
||||
description?: string;
|
||||
fulfills?: { "@type": string; identifier?: string; lastClaimId?: string }[];
|
||||
identifier?: string;
|
||||
image?: string;
|
||||
object?: { amountOfThisGood: number; unitCode: string };
|
||||
recipient?: { identifier: string };
|
||||
}
|
||||
@@ -183,7 +187,7 @@ export interface PlanData {
|
||||
rowid?: string;
|
||||
}
|
||||
|
||||
export interface RateLimits {
|
||||
export interface EndorserRateLimits {
|
||||
doneClaimsThisWeek: string;
|
||||
doneRegistrationsThisMonth: string;
|
||||
maxClaimsPerWeek: string;
|
||||
@@ -192,6 +196,12 @@ export interface RateLimits {
|
||||
nextWeekBeginDateTime: string;
|
||||
}
|
||||
|
||||
export interface ImageRateLimits {
|
||||
doneImagesThisWeek: string;
|
||||
maxImagesPerWeek: string;
|
||||
nextWeekBeginDateTime: string;
|
||||
}
|
||||
|
||||
export interface VerifiableCredential {
|
||||
"@context": string;
|
||||
"@type": string;
|
||||
@@ -434,6 +444,7 @@ export async function createAndSubmitGive(
|
||||
fulfillsProjectHandleId?: string,
|
||||
fulfillsOfferHandleId?: string,
|
||||
isTrade: boolean = false,
|
||||
imageUrl?: string,
|
||||
): Promise<CreateAndSubmitClaimResult> {
|
||||
const vcClaim: GiveVerifiableCredential = {
|
||||
"@context": "https://schema.org",
|
||||
@@ -460,6 +471,9 @@ export async function createAndSubmitGive(
|
||||
identifier: fulfillsOfferHandleId,
|
||||
});
|
||||
}
|
||||
if (imageUrl) {
|
||||
vcClaim.image = imageUrl;
|
||||
}
|
||||
return createAndSubmitClaim(
|
||||
vcClaim as GenericServerRecord,
|
||||
identity,
|
||||
@@ -780,8 +794,8 @@ export const claimSpecialDescription = (
|
||||
};
|
||||
|
||||
export const BVC_MEETUPS_PROJECT_CLAIM_ID =
|
||||
//"https://endorser.ch/entity/01GXYPFF7FA03NXKPYY142PY4H";
|
||||
"https://endorser.ch/entity/01HNTZYJJXTGT0EZS3VEJGX7AK";
|
||||
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) => {
|
||||
return {
|
||||
|
||||
@@ -12,9 +12,6 @@ import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
|
||||
import { GenericServerRecord, 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 =
|
||||
@@ -238,6 +235,19 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
|
||||
return newId.did;
|
||||
};
|
||||
|
||||
function getBase64(subscription: PushSubscription, key: PushEncryptionKeyName) {
|
||||
const buffer = subscription.getKey(key);
|
||||
if (!buffer) {
|
||||
return null;
|
||||
}
|
||||
const value = Buffer.from(buffer);
|
||||
return value
|
||||
.toString("base64")
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=+$/, "");
|
||||
}
|
||||
|
||||
export const sendTestThroughPushServer = async (
|
||||
subscription: PushSubscription,
|
||||
skipFilter: boolean,
|
||||
@@ -254,18 +264,8 @@ 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 authB64 = getBase64(subscription, "auth");
|
||||
const p256dhB64 = getBase64(subscription, "p256dh");
|
||||
const newPayload = {
|
||||
endpoint: subscription.endpoint,
|
||||
keys: {
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
faBitcoinSign,
|
||||
faBurst,
|
||||
faCalendar,
|
||||
faCamera,
|
||||
faCheck,
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
faCircle,
|
||||
@@ -76,6 +78,8 @@ library.add(
|
||||
faBitcoinSign,
|
||||
faBurst,
|
||||
faCalendar,
|
||||
faCamera,
|
||||
faCheck,
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
faCircle,
|
||||
@@ -127,9 +131,11 @@ library.add(
|
||||
);
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import Camera from "simple-vue-camera";
|
||||
|
||||
createApp(App)
|
||||
.component("fa", FontAwesomeIcon)
|
||||
.component("camera", Camera)
|
||||
.use(createPinia())
|
||||
.use(VueAxios, axios)
|
||||
.use(router)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -31,213 +31,154 @@ 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("../views/GiftedDetails.vue"),
|
||||
},
|
||||
{
|
||||
path: "/help",
|
||||
name: "help",
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "help" */ "../views/HelpView.vue"),
|
||||
component: () => import("../views/HelpView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "home" */ "../views/HomeView.vue"),
|
||||
component: () => import("../views/HomeView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/help-notifications",
|
||||
name: "help-notifications",
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "help-notifications" */ "../views/HelpNotificationsView.vue"
|
||||
),
|
||||
component: () => import("../views/HelpNotificationsView.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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -96,7 +96,7 @@
|
||||
<!-- Registration notice -->
|
||||
<!-- We won't show any loading indicator because it usually doesn't change anything. We'll just pop the message in only if we discover that they need it. -->
|
||||
<div
|
||||
v-if="!loadingLimits && !limits?.nextWeekBeginDateTime"
|
||||
v-if="!loadingLimits && !endorserLimits?.nextWeekBeginDateTime"
|
||||
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
|
||||
>
|
||||
<p class="mb-4">
|
||||
@@ -105,7 +105,7 @@
|
||||
</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>
|
||||
@@ -157,31 +157,42 @@
|
||||
<div>
|
||||
{{ limitsMessage }}
|
||||
</div>
|
||||
<div v-if="!!limits?.nextWeekBeginDateTime">
|
||||
<p class="mb-3 text-sm">
|
||||
You have done <b>{{ limits.doneClaimsThisWeek }}</b> claims out of
|
||||
<b>{{ limits.maxClaimsPerWeek }}</b> for this week. Your claims
|
||||
counter resets at
|
||||
<b class="whitespace-nowrap">{{
|
||||
readableTime(limits.nextWeekBeginDateTime)
|
||||
}}</b>
|
||||
</p>
|
||||
<div v-if="!!endorserLimits?.nextWeekBeginDateTime">
|
||||
<p class="text-sm">
|
||||
You have done
|
||||
<b>{{ limits.doneRegistrationsThisMonth }}</b> registrations out of
|
||||
<b>{{ limits.maxRegistrationsPerMonth }}</b> for this month.
|
||||
<b>{{ endorserLimits.doneClaimsThisWeek }} claims</b> out of
|
||||
<b>{{ endorserLimits.maxClaimsPerWeek }}</b> for this week. Your
|
||||
claims counter resets at
|
||||
<b class="whitespace-nowrap">{{
|
||||
readableDate(endorserLimits.nextWeekBeginDateTime)
|
||||
}}</b>
|
||||
</p>
|
||||
<p class="mt-3 text-sm">
|
||||
You have done
|
||||
<b>{{ endorserLimits.doneRegistrationsThisMonth }} registrations</b>
|
||||
out of <b>{{ endorserLimits.maxRegistrationsPerMonth }}</b> for this
|
||||
month.
|
||||
<i
|
||||
>(You can register nobody on your first day, and after that only one
|
||||
a day in your first month.)</i
|
||||
>
|
||||
Your registration counter resets at
|
||||
<b class="whitespace-nowrap">
|
||||
{{ readableTime(limits.nextMonthBeginDateTime) }}
|
||||
{{ readableDate(endorserLimits.nextMonthBeginDateTime) }}
|
||||
</b>
|
||||
</p>
|
||||
<p class="mt-3 text-sm" v-if="!!imageLimits">
|
||||
You have uploaded
|
||||
<b>{{ imageLimits?.doneImagesThisWeek }} images</b> out of
|
||||
<b>{{ imageLimits?.maxImagesPerWeek }}</b> for this week. Your image
|
||||
counter resets at
|
||||
<b class="whitespace-nowrap">{{
|
||||
readableDate(imageLimits?.nextWeekBeginDateTime)
|
||||
}}</b>
|
||||
</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
|
||||
@@ -193,14 +204,14 @@
|
||||
<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 mb-6"
|
||||
@click="exportDatabase()"
|
||||
>
|
||||
Download Settings & Contacts
|
||||
@@ -210,7 +221,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"
|
||||
>
|
||||
If no download happened yet, click again here to download now.
|
||||
</a>
|
||||
@@ -291,7 +302,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>
|
||||
@@ -439,9 +450,15 @@
|
||||
{{ DEFAULT_PUSH_SERVER }}
|
||||
</span>
|
||||
|
||||
<div class="mt-2">
|
||||
<span class="text-slate-500 text-sm font-bold">Image Server URL</span>
|
||||
|
||||
<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 -->
|
||||
@@ -471,7 +488,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
|
||||
@@ -486,7 +503,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-1.5 py-2 rounded-md mb-2"
|
||||
>
|
||||
See Global Animated History of Giving
|
||||
</router-link>
|
||||
@@ -499,7 +516,6 @@
|
||||
<script lang="ts">
|
||||
import { AxiosError, AxiosRequestConfig } from "axios";
|
||||
import Dexie from "dexie";
|
||||
import "dexie-export-import";
|
||||
import { ImportProgress } from "dexie-export-import/dist/import";
|
||||
import { ref } from "vue";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
@@ -509,6 +525,7 @@ import QuickNav from "@/components/QuickNav.vue";
|
||||
import TopMessage from "@/components/TopMessage.vue";
|
||||
import {
|
||||
AppString,
|
||||
DEFAULT_IMAGE_API_SERVER,
|
||||
DEFAULT_PUSH_SERVER,
|
||||
NotificationIface,
|
||||
} from "@/constants/app";
|
||||
@@ -516,10 +533,12 @@ import { db, accountsDB } from "@/db/index";
|
||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
import { ErrorResponse, RateLimits } from "@/libs/endorserServer";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const Buffer = require("buffer/").Buffer;
|
||||
import {
|
||||
ErrorResponse,
|
||||
EndorserRateLimits,
|
||||
ImageRateLimits,
|
||||
} from "@/libs/endorserServer";
|
||||
import { Buffer } from "buffer/";
|
||||
|
||||
interface IAccount {
|
||||
did: string;
|
||||
@@ -530,19 +549,24 @@ 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 = "";
|
||||
apiServerInput = "";
|
||||
derivationPath = "";
|
||||
downloadUrl = ""; // because DuckDuckGo doesn't download on automated call to "click" on the anchor
|
||||
endorserLimits: EndorserRateLimits | null = null;
|
||||
givenName = "";
|
||||
imageLimits: ImageRateLimits | null = null;
|
||||
isRegistered = false;
|
||||
isSubscribed = false;
|
||||
notificationMaybeChanged = false;
|
||||
@@ -550,7 +574,6 @@ export default class AccountViewView extends Vue {
|
||||
publicBase64 = "";
|
||||
webPushServer = "";
|
||||
webPushServerInput = "";
|
||||
limits: RateLimits | null = null;
|
||||
limitsMessage = "";
|
||||
loadingLimits = false;
|
||||
showContactGives = false;
|
||||
@@ -697,7 +720,7 @@ export default class AccountViewView extends Vue {
|
||||
this.updateShowShortcutBvc(this.showShortcutBvc);
|
||||
}
|
||||
|
||||
readableTime(timeStr: string) {
|
||||
readableDate(timeStr: string) {
|
||||
return timeStr.substring(0, timeStr.indexOf("T"));
|
||||
}
|
||||
|
||||
@@ -1038,11 +1061,11 @@ export default class AccountViewView extends Vue {
|
||||
this.limitsMessage = "";
|
||||
|
||||
try {
|
||||
const resp = await this.fetchRateLimits(identity);
|
||||
const resp = await this.fetchEndorserRateLimits(identity);
|
||||
if (resp.status === 200) {
|
||||
this.limits = resp.data;
|
||||
this.endorserLimits = resp.data;
|
||||
if (!this.isRegistered) {
|
||||
// the user is not known to be registered, but they are so let's record it
|
||||
// the user was not known to be registered, but now they are (because we got no error) so let's record it
|
||||
try {
|
||||
await db.open();
|
||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
@@ -1062,6 +1085,10 @@ export default class AccountViewView extends Vue {
|
||||
);
|
||||
}
|
||||
}
|
||||
const imageResp = await this.fetchImageRateLimits(identity);
|
||||
if (imageResp.status === 200) {
|
||||
this.imageLimits = imageResp.data;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.handleRateLimitsError(error);
|
||||
@@ -1082,17 +1109,29 @@ export default class AccountViewView extends Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches rate limits from the server.
|
||||
* Fetches rate limits from the Endorser server.
|
||||
*
|
||||
* @param {IIdentifier} identity - The identity object to check rate limits for.
|
||||
* @returns {Promise<AxiosResponse>} The Axios response object.
|
||||
*/
|
||||
private async fetchRateLimits(identity: IIdentifier) {
|
||||
private async fetchEndorserRateLimits(identity: IIdentifier) {
|
||||
const url = `${this.apiServer}/api/report/rateLimits`;
|
||||
const headers = await this.getHeaders(identity);
|
||||
return await this.axios.get(url, { headers } as AxiosRequestConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches rate limits from the image server.
|
||||
*
|
||||
* @param {IIdentifier} identity - The identity object to check rate limits for.
|
||||
* @returns {Promise<AxiosResponse>} The Axios response object.
|
||||
*/
|
||||
private async fetchImageRateLimits(identity: IIdentifier) {
|
||||
const url = DEFAULT_IMAGE_API_SERVER + "/image-limits";
|
||||
const headers = await this.getHeaders(identity);
|
||||
return await this.axios.get(url, { headers } as AxiosRequestConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors that occur while fetching rate limits.
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -32,12 +32,12 @@
|
||||
<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"
|
||||
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"
|
||||
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"
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -67,12 +67,12 @@
|
||||
<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"
|
||||
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"
|
||||
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"
|
||||
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>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<span>
|
||||
<a
|
||||
@click="showHintsForOnboarding()"
|
||||
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"
|
||||
>
|
||||
Onboarding Guide
|
||||
</a>
|
||||
@@ -22,7 +22,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 +75,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 +100,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 +137,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 +145,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 +161,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 +176,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 +187,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 +229,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" />
|
||||
@@ -313,8 +313,8 @@ 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 },
|
||||
|
||||
428
src/views/GiftedDetails.vue
Normal file
428
src/views/GiftedDetails.vue
Normal file
@@ -0,0 +1,428 @@
|
||||
<template>
|
||||
<QuickNav />
|
||||
<TopMessage />
|
||||
|
||||
<!-- CONTENT -->
|
||||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||
<!-- Back -->
|
||||
<div class="text-lg text-center font-light relative px-7">
|
||||
<h1
|
||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||
@click="cancel()"
|
||||
>
|
||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Heading -->
|
||||
<h1 class="text-4xl text-center font-light px-4 mb-4">What Was Given</h1>
|
||||
|
||||
<h1 class="text-xl font-bold text-center mb-4">
|
||||
{{ message }} {{ giverName || "somebody not named" }}
|
||||
</h1>
|
||||
<textarea
|
||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||
placeholder="What was received"
|
||||
v-model="description"
|
||||
/>
|
||||
<div class="flex flex-row justify-center">
|
||||
<span
|
||||
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center text-blue-500 px-2 py-2 w-20"
|
||||
@click="changeUnitCode()"
|
||||
>
|
||||
{{ libsUtil.UNIT_SHORT[unitCode] }}
|
||||
</span>
|
||||
<div
|
||||
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
||||
@click="amountInput === '0' ? null : decrement()"
|
||||
>
|
||||
<fa icon="chevron-left" />
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
||||
v-model="amountInput"
|
||||
/>
|
||||
<div
|
||||
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
||||
@click="increment()"
|
||||
>
|
||||
<fa icon="chevron-right" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center mt-4">
|
||||
<span v-if="imageUrl" class="flex justify-between">
|
||||
<a :href="imageUrl" target="_blank" class="text-blue-500 ml-4">
|
||||
<img :src="imageUrl" class="h-24 rounded-xl" />
|
||||
</a>
|
||||
<fa
|
||||
icon="trash-can"
|
||||
@click="confirmDeleteImage"
|
||||
class="text-red-500 fa-fw ml-8 mt-10"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
<fa
|
||||
icon="camera"
|
||||
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>
|
||||
</div>
|
||||
<GiftedPhotoDialog ref="photoDialog" />
|
||||
|
||||
<div v-if="projectId" class="mt-4">
|
||||
<fa
|
||||
icon="check"
|
||||
class="bg-slate-500 text-white h-5 w-5 px-0.5 py-0.5 mr-2 rounded"
|
||||
/>
|
||||
<label class="text-sm">This is given to a project</label>
|
||||
</div>
|
||||
|
||||
<div v-if="!projectId" class="mt-4">
|
||||
<input type="checkbox" class="h-6 w-6 mr-2" v-model="givenToUser" />
|
||||
<label class="text-sm">Given to you</label>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<input type="checkbox" class="h-6 w-6 mr-2" v-model="isTrade" />
|
||||
<label class="text-sm">Trade (not a gift)</label>
|
||||
</div>
|
||||
|
||||
<p class="text-center mb-2 mt-6 italic">
|
||||
Sign & Send to publish to the world
|
||||
</p>
|
||||
<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="confirm"
|
||||
>
|
||||
Sign & 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>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import TopMessage from "@/components/TopMessage.vue";
|
||||
import { db } from "@/db/index";
|
||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||
import { createAndSubmitGive } from "@/libs/endorserServer";
|
||||
import * as libsUtil from "@/libs/util";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||
import GiftedPhotoDialog from "@/components/GiftedPhotoDialog.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
GiftedDialog,
|
||||
GiftedPhotoDialog,
|
||||
QuickNav,
|
||||
TopMessage,
|
||||
},
|
||||
})
|
||||
export default class GiftedDetails extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
|
||||
activeDid = "";
|
||||
apiServer = "";
|
||||
|
||||
amountInput = "0";
|
||||
description = "";
|
||||
givenToUser = false;
|
||||
giverDid: string | undefined;
|
||||
giverName = "";
|
||||
imageUrl = "";
|
||||
isTrade = false;
|
||||
message = "";
|
||||
offerId = "";
|
||||
projectId = "";
|
||||
unitCode = "HUR";
|
||||
|
||||
libsUtil = libsUtil;
|
||||
|
||||
async mounted() {
|
||||
this.amountInput = this.$route.query.amountInput as string;
|
||||
this.description = this.$route.query.description as string;
|
||||
this.giverDid = this.$route.query.giverDid as string;
|
||||
this.giverName = this.$route.query.giverName as string;
|
||||
this.message = this.$route.query.message as string;
|
||||
this.offerId = this.$route.query.offerId as string;
|
||||
this.projectId = this.$route.query.projectId as string;
|
||||
this.unitCode = this.$route.query.unitCode as string;
|
||||
|
||||
this.imageUrl = localStorage.getItem("imageUrl") || "";
|
||||
|
||||
this.givenToUser = !this.projectId;
|
||||
|
||||
try {
|
||||
await db.open();
|
||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
||||
this.apiServer = settings?.apiServer || "";
|
||||
this.activeDid = settings?.activeDid || "";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
console.error("Error retrieving settings from database:", err);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: err.message || "There was an error retrieving your settings.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
changeUnitCode() {
|
||||
const units = Object.keys(this.libsUtil.UNIT_SHORT);
|
||||
const index = units.indexOf(this.unitCode);
|
||||
this.unitCode = units[(index + 1) % units.length];
|
||||
}
|
||||
|
||||
increment() {
|
||||
this.amountInput = `${(parseFloat(this.amountInput) || 0) + 1}`;
|
||||
}
|
||||
|
||||
decrement() {
|
||||
this.amountInput = `${Math.max(
|
||||
0,
|
||||
(parseFloat(this.amountInput) || 1) - 1,
|
||||
)}`;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.deleteImage(); // not awaiting, so they'll go back immediately
|
||||
this.$router.back();
|
||||
}
|
||||
|
||||
openPhotoDialog() {
|
||||
(this.$refs.photoDialog as GiftedPhotoDialog).open((imgUrl) => {
|
||||
this.imageUrl = imgUrl;
|
||||
});
|
||||
}
|
||||
|
||||
confirmDeleteImage() {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Are you sure you want to delete the image?",
|
||||
text: "",
|
||||
onYes: this.deleteImage,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
async deleteImage() {
|
||||
if (!this.imageUrl) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const identity = await libsUtil.getIdentity(this.activeDid);
|
||||
const token = await accessToken(identity);
|
||||
const response = await this.axios.delete(
|
||||
DEFAULT_IMAGE_API_SERVER +
|
||||
"/image/" +
|
||||
encodeURIComponent(this.imageUrl),
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (response.status === 204) {
|
||||
// don't bother with a notification
|
||||
// (either they'll simply continue or they're canceling and going back)
|
||||
} else {
|
||||
console.error("Non-success deleting image:", response);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "There was a problem deleting the image.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
// keep the imageUrl in localStorage so the user can try again if they want
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem("imageUrl");
|
||||
this.imageUrl = "";
|
||||
} catch (error) {
|
||||
console.error("Error deleting image:", error);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((error as any).response.status === 404) {
|
||||
console.log("The image was already deleted:", error);
|
||||
|
||||
localStorage.removeItem("imageUrl");
|
||||
this.imageUrl = "";
|
||||
|
||||
// it already doesn't exist so we won't say anything to the user
|
||||
} else {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "There was an error deleting the image.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async confirm() {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
text: "Recording the give...",
|
||||
title: "",
|
||||
},
|
||||
1000,
|
||||
);
|
||||
// this is asynchronous, but we don't need to wait for it to complete
|
||||
await this.recordGive();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param giverDid may be null
|
||||
* @param description may be an empty string
|
||||
* @param amountInput may be 0
|
||||
* @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(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
identity,
|
||||
this.giverDid,
|
||||
this.givenToUser ? this.activeDid : undefined,
|
||||
this.description,
|
||||
parseFloat(this.amountInput),
|
||||
this.unitCode,
|
||||
this.projectId,
|
||||
this.offerId,
|
||||
this.isTrade,
|
||||
this.imageUrl,
|
||||
);
|
||||
|
||||
if (
|
||||
result.type === "error" ||
|
||||
this.isGiveCreationError(result.response)
|
||||
) {
|
||||
const errorMessage = this.getGiveCreationErrorMessage(result);
|
||||
console.error("Error with give creation result:", result);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: errorMessage || "There was an error creating the give.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
} else {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Success",
|
||||
text: `That ${this.isTrade ? "trade" : "gift"} was recorded.`,
|
||||
},
|
||||
5000,
|
||||
);
|
||||
localStorage.removeItem("imageUrl");
|
||||
this.$router.back();
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
console.error("Error with give recordation caught:", error);
|
||||
const message =
|
||||
error.userMessage ||
|
||||
error.response?.data?.error?.message ||
|
||||
"There was an error recording the give.";
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: message,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for readability
|
||||
|
||||
/**
|
||||
* @param result response "data" from the server
|
||||
* @returns true if the result indicates an error
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
isGiveCreationError(result: any) {
|
||||
return result.status !== 201 || result.data?.error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
|
||||
* @returns best guess at an error message
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getGiveCreationErrorMessage(result: any) {
|
||||
return (
|
||||
result.error?.userMessage ||
|
||||
result.error?.error ||
|
||||
result.response?.data?.error?.message
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -217,6 +217,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 +262,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
|
||||
@@ -333,7 +385,7 @@ export default class Help extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
|
||||
package = Package;
|
||||
commitHash = process.env.VUE_APP_GIT_HASH;
|
||||
commitHash = import.meta.env.VITE_GIT_HASH;
|
||||
|
||||
showOnboardInfo() {
|
||||
this.$notify(
|
||||
|
||||
@@ -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
|
||||
>
|
||||
@@ -152,13 +152,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 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"
|
||||
>
|
||||
Ideas...
|
||||
</button>
|
||||
@@ -225,6 +225,11 @@
|
||||
</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="record.image" class="flex justify-center">
|
||||
<a :href="record.image" target="_blank">
|
||||
<img :src="record.image" class="h-24 mt-2 rounded-xl" />
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</InfiniteScroll>
|
||||
@@ -267,6 +272,7 @@ interface GiveRecordWithContactInfo extends GiveServerRecord {
|
||||
displayName: string;
|
||||
known: boolean;
|
||||
};
|
||||
image: string;
|
||||
receiver: {
|
||||
displayName: string;
|
||||
known: boolean;
|
||||
@@ -427,6 +433,7 @@ export default class HomeView extends Vue {
|
||||
contactForDid(giverDid, this.allContacts),
|
||||
this.allMyDids,
|
||||
),
|
||||
image: claim.image,
|
||||
receiver: didInfoForContact(
|
||||
recipientDid,
|
||||
this.activeDid,
|
||||
|
||||
@@ -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…
|
||||
</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
|
||||
|
||||
@@ -58,14 +58,14 @@
|
||||
<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"
|
||||
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"
|
||||
>
|
||||
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"
|
||||
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>
|
||||
|
||||
@@ -51,14 +51,14 @@
|
||||
<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"
|
||||
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"
|
||||
>
|
||||
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"
|
||||
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>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<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"
|
||||
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
|
||||
@@ -32,7 +32,7 @@
|
||||
<!-- 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"
|
||||
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
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<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"
|
||||
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()"
|
||||
>
|
||||
<!-- SHOW if in idle state -->
|
||||
@@ -121,7 +121,7 @@
|
||||
</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"
|
||||
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
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
<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>
|
||||
@@ -176,7 +176,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…
|
||||
</a>
|
||||
@@ -437,15 +437,6 @@ export default class ProjectViewView extends Vue {
|
||||
return identity;
|
||||
}
|
||||
|
||||
public async getHeaders(identity: IIdentifier) {
|
||||
const token = await accessToken(identity);
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
onEditClick() {
|
||||
localStorage.setItem("projectId", this.projectId as string);
|
||||
const route = {
|
||||
|
||||
@@ -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>
|
||||
@@ -142,7 +142,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
if (timeResult.type === "success") {
|
||||
timeSuccess = true;
|
||||
} else {
|
||||
console.error("Error sending give:", timeResult);
|
||||
console.error("Error sending time:", timeResult);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -169,7 +169,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
if (attendResult.type === "success") {
|
||||
attendedSuccess = true;
|
||||
} else {
|
||||
console.error("Error sending give:", attendResult);
|
||||
console.error("Error sending attendance:", attendResult);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -187,16 +187,16 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
if (timeSuccess || attendedSuccess) {
|
||||
const actions =
|
||||
timeSuccess && attendedSuccess
|
||||
? "attendance and time have been"
|
||||
? "Your attendance and time have been recorded."
|
||||
: timeSuccess
|
||||
? "time has been"
|
||||
: "attendance has been";
|
||||
? "Your time has been recorded."
|
||||
: "Your attendance has been recorded.";
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Success",
|
||||
text: `Your ${actions} recorded.`,
|
||||
text: actions,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
|
||||
@@ -26,13 +26,6 @@
|
||||
</div>
|
||||
<div v-else-if="claimsToConfirm.length === 0">
|
||||
There are no claims yet today for you to confirm.
|
||||
<span v-if="claimCountWithHidden > 0">
|
||||
{{
|
||||
claimCountWithHidden === 1
|
||||
? "(There is 1 claim with hidden details.)"
|
||||
: `(There are ${claimCountWithHidden} claims with hidden details.)`
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<ul class="border-t border-slate-300 m-2">
|
||||
<li
|
||||
@@ -76,6 +69,19 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="claimCountWithHidden > 0" class="border-b border-slate-300 pb-2">
|
||||
<span>
|
||||
{{
|
||||
claimCountWithHidden === 1
|
||||
? "There is 1 other claim with hidden details,"
|
||||
: `There are ${claimCountWithHidden} other claims with hidden details,`
|
||||
}}
|
||||
so if you expected but do not see details from someone then ask them to
|
||||
check that their activity is visible to you on their Contacts
|
||||
<fa icon="users" class="text-slate-500" />
|
||||
page.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-2xl m-2">Anything else?</h2>
|
||||
@@ -89,6 +95,8 @@
|
||||
size="20"
|
||||
class="border border-slate-400 h-6 px-2"
|
||||
/>
|
||||
<br />
|
||||
(Everyone likes personalized messages! 😁)
|
||||
</span>
|
||||
<!-- This is to match input height to avoid shifting when hiding & showing. -->
|
||||
<span v-else class="h-6">...</span>
|
||||
@@ -101,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>
|
||||
@@ -320,7 +328,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
title: "Error",
|
||||
text:
|
||||
(giveResult as ErrorResult)?.error?.userMessage ||
|
||||
"There was an error sending the give.",
|
||||
"There was an error sending that give.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
@@ -332,19 +340,20 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
confirmsSucceeded.length === 1 ? "confirmation" : "confirmations";
|
||||
const actions =
|
||||
confirmsSucceeded.length > 0 && giveSucceeded
|
||||
? `${confirms} and give have been`
|
||||
? `Your ${confirms} and that give have been recorded.`
|
||||
: giveSucceeded
|
||||
? "give has been"
|
||||
: confirms +
|
||||
? "That give has been recorded."
|
||||
: "Your " +
|
||||
confirms +
|
||||
" " +
|
||||
(confirmsSucceeded.length === 1 ? "has" : "have") +
|
||||
" been";
|
||||
" been recorded.";
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Success",
|
||||
text: `Your ${actions} recorded.`,
|
||||
text: actions,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,20 +36,20 @@
|
||||
</p>
|
||||
<a
|
||||
@click="onClickYes()"
|
||||
class="block w-full text-center text-lg uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
|
||||
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"
|
||||
>
|
||||
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"
|
||||
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-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"
|
||||
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-2"
|
||||
>
|
||||
Derive new address from existing seed
|
||||
</a>
|
||||
|
||||
@@ -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
18
vite.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import * as path from "path";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 8080
|
||||
},
|
||||
plugins: [ vue() ],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
buffer: path.resolve(__dirname, 'node_modules', 'buffer'),
|
||||
'dexie-export-import/dist/import': 'dexie-export-import/dist/import/index.js',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -2,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.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",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user