Compare commits

...

48 Commits

Author SHA1 Message Date
Jose Olarte III
b4b78f6a2c Recolored buttons 2023-12-18 19:46:58 +08:00
Jose Olarte III
3c0f6ce0de Design and uniformity tweaks 2023-12-18 19:44:03 +08:00
5534f8fa50 fix logic for prod & test host detection 2023-12-17 20:17:45 -07:00
a5004d475e bump version to next -beta 2023-12-17 20:02:28 -07:00
b445b1234f bump version to 0.1.6 2023-12-17 20:00:12 -07:00
17c96dd01a fix linting, etc with previous feature (env warning) 2023-12-16 17:17:54 -07:00
6ad17101b2 Merge pull request 'add warning if on unexpected server' (#98) from server-warn into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#98
2023-12-16 10:08:17 -05:00
b4085ffaa7 Merge branch 'master' into server-warn 2023-12-16 08:08:00 -07:00
4f2cb55753 add warning if on unexpected server 2023-12-16 08:04:16 -07:00
ebf9164ecc Merge pull request 'add infinite scroll to the home page feed' (#97) from home-infinite into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#97
2023-12-15 07:49:01 -05:00
540cc21839 add infinite scroll to the home page feed 2023-12-14 21:54:15 -07:00
c182068901 give more details on map-overlap issue 2023-12-13 21:27:13 -07:00
aaa1f31945 fix one single linting problem 2023-12-13 21:16:17 -07:00
17c632eb16 on brand new ID, go back home (plus some task adjustments) 2023-12-13 20:19:29 -07:00
41c4cbe61a fix more messaging and actions if they don't have an ID 2023-12-13 20:05:58 -07:00
c8402797ad remove "mute" notifications (since they can turn them off, and the mute functionality isn't built) 2023-12-13 19:53:46 -07:00
4a09b9b9b1 more fixes for when there is no identity 2023-12-13 19:46:40 -07:00
5db3423301 enhance error messages 2023-12-13 19:17:18 -07:00
2b00b243e8 fix error when lacking ID, and format linked projects 2023-12-13 19:13:04 -07:00
f2e5d8168d Merge pull request 'design-tweaks-2023-12' (#96) from design-tweaks-2023-12 into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#96
2023-12-13 11:20:54 -05:00
1d262b8da9 Merge branch 'master' into design-tweaks-2023-12 2023-12-13 11:17:33 -05:00
8ed74b71f2 change colors and add spacing to make buttons obvious 2023-12-13 09:16:45 -07:00
8fb21c3d89 doc: update tasks, update verbiage, and add to help doc 2023-12-13 08:58:04 -07:00
8dbfcd38d3 fix bad text in pop-up, and fix name of class 2023-12-13 08:57:24 -07:00
04df0d4eff fix problem when there's a null description 2023-12-13 08:56:53 -07:00
Jose Olarte III
ab523639a5 Normalized button visual styles 2023-12-13 18:48:45 +08:00
Jose Olarte III
0484dfb253 Spacing and typography fixes 2023-12-13 18:37:03 +08:00
Jose Olarte III
c2839e8a99 Mobile-style flushed-right toggle switches 2023-12-13 18:22:15 +08:00
Jose Olarte III
e533cd3d34 Visual improvements to "set name" button 2023-12-13 18:16:15 +08:00
Jose Olarte III
18e00b95c7 Fixed size and alignment of QR code button 2023-12-13 17:41:27 +08:00
Jose Olarte III
e97cd1b1fa Minor visual improvements in "giving recognition" section 2023-12-13 17:29:53 +08:00
ccca93b9f1 change some messages, rework tasks 2023-12-11 19:19:29 -07:00
1be6c04699 prompt them to fill in their name when sharing their info 2023-12-11 16:36:50 -07:00
2c33febb0e fix location of web-push unsubscribe action 2023-12-10 20:22:41 -07:00
e6f73dc81c add an unsubscribe to the web push 2023-12-10 20:17:14 -07:00
0d55a722c5 there's no need to capitalize EVERYTHING 2023-12-10 18:55:13 -07:00
97ef78f5dd add better debug logging for web-push info 2023-12-10 18:53:58 -07:00
672abac9a9 show web-push subscription info on demand, and refine docs 2023-12-10 18:43:24 -07:00
0607fad3e5 remove the 'never' option for notifications & close on 'maybe later' 2023-12-10 17:41:03 -07:00
6aa89a1d1d update icon and favicon 2023-12-10 16:53:35 -07:00
2556d5feb9 Merge pull request 'add hash to help page for tracking exact versions' (#95) from git-hash into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#95
2023-12-10 11:21:23 -05:00
3c1654764c add commit hash to help page 2023-12-10 09:20:03 -07:00
4c1e229d62 compute commit hash with git-describe 2023-12-10 09:18:07 -07:00
17444d75de remove references to test file 2023-12-10 08:59:25 -07:00
f2fb432d2e update documentation for going to production 2023-12-09 22:15:24 -07:00
e45689daed bump version to next beta 2023-12-09 21:21:35 -07:00
041308ebc9 fix app name so that it'll build 2023-12-09 20:39:17 -07:00
9c36bb509a bump to v 0.1.5 and other misc tweaks 2023-12-09 20:36:51 -07:00
31 changed files with 778 additions and 350 deletions

View File

@@ -8,8 +8,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.1.6] - 2023.12.17
### Added ### Added
- Web push notifications - Infinite scroll on home page
### Changed
- UI improvements
- Show web-push subscription info
- Icon
## [0.1.5] - 2023.12.09 - 9c36bb509a9bae9bb3306d3bd9eeb144b67aa8ad
### Added
- Web push notifications (though not finalized)
- Credentials details page
- See more data without an ID
- Change units of a give
## [0.1.4] - 2023.11.20 - 7311d36726f3667ec4c68f241f91d404273ad4db
### Added
- Offer on a project
### Changed
- Automatically set as visible when importing a contact
## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde ## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde
### Added ### Added

View File

@@ -22,16 +22,25 @@ npm run lint
If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",` If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
``` * `npx prettier --write ./sw_scripts/`
npm run build
```
``` ...to make sure the service worker scripts are in proper form
npx prettier --write ./sw_scripts/
```
to make sure the service worker scripts are in proper form
... then copy the contents of the `sw_scripts` folder to the `dist` folder - except additional_scripts.js. * Update the CHANGELOG.md & the version in package.json, run `npm install`, and commit.
* Tag wth the new version: `git tag 0.1.0`. Increment version, add "-beta", `npm install`, and commit.
* If production, change src/constants/app.ts DEFAULT_*_SERVER to be PROD.
* `npm run build`
* Revert src/constants/app.ts
* `cp sw_scripts/[ns]* dist/`
... to copy the contents of the `sw_scripts` folder to the `dist` folder - except additional_scripts.js.
* `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntu@endorser.ch:time-safari`
@@ -103,9 +112,9 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib
### Clear/Reset data & restart ### Clear/Reset data & restart
* Clear cache for site. (In Chrome, go to `chrome://settings/cookies` and "all site data and permissions"; in Firefox, go to `about:preferences` and search for cache.) * Clear cache for site. (In Chrome, go to `chrome://settings/cookies` and "all site data and permissions"; in Firefox, go to `about:preferences` and search for "cache" then "Manage Data".)
* Unregister service worker (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers` or `about:debugging`). * Unregister service worker (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers`).
* Clear notification permission (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search). * Clear notification permission (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search for "notifications").
* Clear Cache Storage (in Chrome, in dev tools under Application; in Firefox, in dev tools under Storage). * Clear Cache Storage (in Chrome, in dev tools under Application; in Firefox, in dev tools under Storage).

39
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "crowd-funder-for-time-pwa", "name": "TimeSafari",
"version": "0.1.4", "version": "0.1.7-beta",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "crowd-funder-for-time-pwa", "name": "TimeSafari",
"version": "0.1.4", "version": "0.1.7-beta",
"dependencies": { "dependencies": {
"@ethersproject/hdnode": "^5.7.0", "@ethersproject/hdnode": "^5.7.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2", "@fortawesome/fontawesome-svg-core": "^6.4.2",
@@ -33,6 +33,7 @@
"ethereum-cryptography": "^2.1.2", "ethereum-cryptography": "^2.1.2",
"ethereumjs-util": "^7.1.5", "ethereumjs-util": "^7.1.5",
"ethr-did-resolver": "^8.1.2", "ethr-did-resolver": "^8.1.2",
"git-describe": "^4.1.1",
"jdenticon": "^3.2.0", "jdenticon": "^3.2.0",
"js-generate-password": "^0.1.9", "js-generate-password": "^0.1.9",
"localstorage-slim": "^2.5.0", "localstorage-slim": "^2.5.0",
@@ -8894,8 +8895,7 @@
"node_modules/@types/semver": { "node_modules/@types/semver": {
"version": "7.5.3", "version": "7.5.3",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz",
"integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw=="
"dev": true
}, },
"node_modules/@types/send": { "node_modules/@types/send": {
"version": "0.17.2", "version": "0.17.2",
@@ -16272,6 +16272,30 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/git-describe": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/git-describe/-/git-describe-4.1.1.tgz",
"integrity": "sha512-JC8ganO5kO80G8+XE98TDDjnMXQN3Estk3qdJuG2EGRF/l6zuMTMcN+8OSfQZ5FWpqIRLB015anWX4aSRgnxAQ==",
"dependencies": {
"@types/semver": "^7.3.8",
"lodash": "^4.17.21"
},
"engines": {
"node": ">=4.0.0"
},
"optionalDependencies": {
"semver": "^5.6.0"
}
},
"node_modules/git-describe/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"optional": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/glob": { "node_modules/glob": {
"version": "7.2.3", "version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -19338,8 +19362,7 @@
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"devOptional": true
}, },
"node_modules/lodash.clonedeep": { "node_modules/lodash.clonedeep": {
"version": "4.5.0", "version": "4.5.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "crowd-funder-for-time-pwa", "name": "TimeSafari",
"version": "0.1.4", "version": "0.1.7-beta",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
@@ -33,6 +33,7 @@
"ethereum-cryptography": "^2.1.2", "ethereum-cryptography": "^2.1.2",
"ethereumjs-util": "^7.1.5", "ethereumjs-util": "^7.1.5",
"ethr-did-resolver": "^8.1.2", "ethr-did-resolver": "^8.1.2",
"git-describe": "^4.1.1",
"jdenticon": "^3.2.0", "jdenticon": "^3.2.0",
"js-generate-password": "^0.1.9", "js-generate-password": "^0.1.9",
"localstorage-slim": "^2.5.0", "localstorage-slim": "^2.5.0",

View File

@@ -1,23 +1,27 @@
tasks: tasks:
- 40 notifications : - 08 notifications :
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew - get it to work on Android - background data in apps
- extract private_key_hex in py-push-server webpush.py - get it to work on iOS
- lock down regenerate_vapid endpoint (so only we admins can do it on demand) - make the app behave correctly when App Notifications are turned off
- remove sleep in py-push-server app.py
- revisit "maybe" and "never" buttons on accont screen
- see if we can detect OS-level notifications if turned off
- write troubleshooting docs for notifications - write troubleshooting docs for notifications
- in py-push-server, when sending a push to a subscriber and we get on a 410 "error #106", delete the subscription record - hide the "App Notifications" toggle when they switch notifications
- prompt user to install on their home screen https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getInstalledRelatedApps
- warn if they're using the web (android only?)
https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getInstalledRelatedApps
https://web.dev/articles/get-installed-related-apps
- add windows & mac help at OS & browser level
- .3 fix the Project-location-selection map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui - back-and-forth on discovery & project pages led to "You need an identity to load your projects." error on product page when I had an identity
- fix the projects on /discover to show the issuer (currently all "Someone Anonymous")
- .5 Add infinite scroll to gifts on the home page - .3 bug - make or edit a project, choose "Include location", and see the map display shows on top of the bottom icons assignee-group:ui
- Got error adding on Firefox user #0 as contact for themselves
- .5 If notifications are not enabled, add message to front page with link/button to enable - .5 If notifications are not enabled, add message to front page with link/button to enable
- add note after contact addition that they can see your info - 01 server - show all claim details when issued by the issuer
- enhance help page instructions for debugging - enhance help page instructions for debugging
- add way to test quickly a push notification - add way to test quickly a push notification
- help instructions for PWA install problems (secret failed, must reinstall) - help instructions for PWA install problems (secret failed, must reinstall)
@@ -30,7 +34,6 @@ tasks:
- add VC confirmation? - add VC confirmation?
- Release Minimum Viable Product : - Release Minimum Viable Product :
- generate new webpush.db entry, webpush.py private_key_hex & subscription_info & vapid_claims email
- .5 deploy endorser.ch server above Dec 1 (to get plan searches by names as well as descriptions) - .5 deploy endorser.ch server above Dec 1 (to get plan searches by names as well as descriptions)
- 08 thorough testing for errors & edge cases - 08 thorough testing for errors & edge cases
- 01 ensure ability to recover server remotely, and add redundant access - 01 ensure ability to recover server remotely, and add redundant access
@@ -43,8 +46,10 @@ tasks:
- Other features - donation vs give, show offers, show give & outstanding totals, show network view, restrict registration, connect to contacts - Other features - donation vs give, show offers, show give & outstanding totals, show network view, restrict registration, connect to contacts
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
- 01 send visibility signal as a VC and store it
- remove 'rowid' references (that are sqlite-specific)
- make identicons for contacts into more-memorable faces (and maybe change project identicons, too) - make identicons for contacts into more-memorable faces (and maybe change project identicons, too)
- allow some gives even if they aren't registered - 01 make the prod build copy the sw_scripts
- .5 Add start date to project - .5 Add start date to project
- .3 check that Android shows "back" buttons on screens without bottom tray - .3 check that Android shows "back" buttons on screens without bottom tray
- .1 Make give description text box into something that expands as they type? - .1 Make give description text box into something that expands as they type?
@@ -58,6 +63,9 @@ tasks:
- switch some checks for activeDid to check for isRegistered - switch some checks for activeDid to check for isRegistered
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show" - .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
- .5 fix cert generation on server (since it didn't happen automatically for Nov 30) - .5 fix cert generation on server (since it didn't happen automatically for Nov 30)
- 04 fix lack of initial notification in Firefox (on MacOS, maybe others)
- contacts v+ : - contacts v+ :
- 01 Import all the non-sensitive data (ie. contacts & settings). - 01 Import all the non-sensitive data (ie. contacts & settings).
- .2 show error to user when adding a duplicate contact - .2 show error to user when adding a duplicate contact
@@ -99,6 +107,8 @@ tasks:
- automated tests, eg. cypress - automated tests, eg. cypress
- Notifications (wake on the phone, push notifications) - Notifications (wake on the phone, push notifications)
- pull instead of push, maybe via scheduled runs
- have a notification pop-up on Mac screen
- Connect with phone contacts - Connect with phone contacts
@@ -111,12 +121,11 @@ tasks:
- Do we want split first name & last name? - Do we want split first name & last name?
- 40 notifications v+ :
- pull, w/ scheduled runs
- 01 On nearby search, if user starts changing their box but cancels and goes back to the map it is zoomed far out. Fix to fit the box better. - 01 On nearby search, if user starts changing their box but cancels and goes back to the map it is zoomed far out. Fix to fit the box better.
- 16 From the home screen, make the quick action even easier. - 16 From the home screen, make the quick action even easier.
- allow some gives even if they aren't registered - maybe someday as a gift to the world, but we really want this to be built via personal connections
log: log:
- videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29 - videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29
- project lists, contact totals & actions, multiple identifiers, stats-world, activity feed, rename of this project file (use "--follow --") milestone:2 done:2023-06-27 - project lists, contact totals & actions, multiple identifiers, stats-world, activity feed, rename of this project file (use "--follow --") milestone:2 done:2023-06-27

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -169,20 +169,12 @@
> >
Turn on Notifications Turn on Notifications
</button> </button>
<div class="grid grid-cols-2 gap-2">
<button <button
@click="maybeLater(notification.id)" @click="close(notification.id)"
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md" class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
> >
Maybe Later Maybe Later
</button> </button>
<button
@click="never(notification.id)"
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md"
>
Never
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -238,6 +230,10 @@
</p> </p>
<button <button
@click="
close(notification.id);
turnOffNotifications();
"
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2" class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
> >
Turn Off Notifications Turn Off Notifications
@@ -332,6 +328,12 @@ export default class App extends Vue {
); );
} }
} catch (error) { } catch (error) {
if (window.location.host.startsWith("localhost")) {
console.log(
"Ignoring this error getting VAPID for local development:",
error,
);
} else {
console.error("Got an error initializing notifications:", error); console.error("Got an error initializing notifications:", error);
this.$notify( this.$notify(
{ {
@@ -344,6 +346,7 @@ export default class App extends Vue {
); );
} }
} }
}
private sendMessageToServiceWorker( private sendMessageToServiceWorker(
message: ServiceWorkerMessage, message: ServiceWorkerMessage,
@@ -410,7 +413,10 @@ export default class App extends Vue {
private requestNotificationPermission(): Promise<NotificationPermission> { private requestNotificationPermission(): Promise<NotificationPermission> {
return Notification.requestPermission().then((permission) => { return Notification.requestPermission().then((permission) => {
if (permission !== "granted") { if (permission !== "granted") {
alert("We need notification permission to provide certain features."); alert(
"Allow this app permission to make notifications for personal reminders." +
" You can adjust them at any time in your settings.",
);
throw new Error("We weren't granted permission."); throw new Error("We weren't granted permission.");
} }
return permission; return permission;
@@ -452,8 +458,13 @@ export default class App extends Vue {
}); });
}) })
.catch((error) => { .catch((error) => {
console.error("An error occurred:", error); console.error(
alert("Some error occurred." + error); "An error occurred setting notification permissions:",
error,
);
alert(
"Some error occurred setting notification permissions. See logs.",
);
}); });
} }
@@ -520,7 +531,7 @@ export default class App extends Vue {
private sendSubscriptionToServer( private sendSubscriptionToServer(
subscription: PushSubscription, subscription: PushSubscription,
): Promise<void> { ): Promise<void> {
console.log(subscription); console.log("About to send subscription", subscription);
return fetch("/web-push/subscribe", { return fetch("/web-push/subscribe", {
method: "POST", method: "POST",
headers: { headers: {
@@ -535,12 +546,49 @@ export default class App extends Vue {
}); });
} }
never(ID: string) { async turnOffNotifications() {
alert(ID); let subscription;
const pushProviderSuccess = await navigator.serviceWorker.ready
.then((registration) => {
return registration.pushManager.getSubscription();
})
.then((subscript) => {
subscription = subscript;
if (subscription) {
return subscription.unsubscribe();
} else {
console.log("Subscription object is not available.");
return false;
} }
})
.catch((error) => {
console.log("Push provider server communication failed:", error);
return false;
});
maybeLater(ID: string) { const pushServerSuccess = await fetch("/web-push/unsubscribe", {
alert(ID); method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(subscription),
})
.then((response) => {
return response.ok;
})
.catch((error) => {
console.log("Push server communication failed:", error);
return false;
});
alert(
"Notifications are off. Push provider unsubscribe " +
(pushProviderSuccess ? "succeeded" : "failed") +
(pushProviderSuccess === pushServerSuccess ? " and" : " but") +
" push server unsubscribe " +
(pushServerSuccess ? "succeeded" : "failed") +
".",
);
} }
} }
</script> </script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -0,0 +1,58 @@
<template>
<div class="text-center text-red-500">{{ message }}</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-facing-decorator";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { AppString } from "@/constants/app";
interface Notification {
group: string;
type: string;
title: string;
text: string;
}
@Component
export default class TopMessage extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
@Prop selected = "";
message = "";
async mounted() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
if (
settings?.warnIfTestServer &&
window.location.hostname !== AppString.PROD_TIME_SAFARI_SERVER_HOST
) {
const didPrefix = settings.activeDid?.slice(11, 14);
this.message = "You're linked to a test server, user " + didPrefix;
} else if (
settings?.warnIfProdServer &&
window.location.hostname === AppString.PROD_TIME_SAFARI_SERVER_HOST
) {
const didPrefix = settings.activeDid?.slice(1, 14);
this.message =
"You're linked to the production server, user " + didPrefix;
}
} catch (err: unknown) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Detecting Server",
text: JSON.stringify(err),
},
10000,
);
}
}
}
</script>

View File

@@ -6,12 +6,16 @@
export enum AppString { export enum AppString {
APP_NAME = "Time Safari", APP_NAME = "Time Safari",
PROD_TIME_SAFARI_SERVER_HOST = "timesafari.app",
PROD_TIME_SAFARI_SERVER = "https://" + PROD_TIME_SAFARI_SERVER_HOST,
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch", PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch", TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
LOCAL_ENDORSER_API_SERVER = "http://localhost:3000", LOCAL_ENDORSER_API_SERVER = "http://localhost:3000",
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER, DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
PROD_PUSH_SERVER = "https://timesafari.app", PROD_PUSH_SERVER = "https://timesafari.app",
TEST1_PUSH_SERVER = "https://test.timesafari.app", TEST1_PUSH_SERVER = "https://test.timesafari.app",
TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com", TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com",

View File

@@ -45,8 +45,5 @@ db.on("populate", () => {
db.settings.add({ db.settings.add({
id: MASTER_SETTINGS_KEY, id: MASTER_SETTINGS_KEY,
apiServer: AppString.DEFAULT_ENDORSER_API_SERVER, apiServer: AppString.DEFAULT_ENDORSER_API_SERVER,
// remember that things you add from now on aren't automatically in the DB for old users
webPushServer: AppString.DEFAULT_PUSH_SERVER,
}); });
}); });

View File

@@ -12,15 +12,17 @@ export type BoundingBox = {
* Settings type encompasses user-specific configuration details. * Settings type encompasses user-specific configuration details.
*/ */
export type Settings = { export type Settings = {
id: number; // Only one entry using MASTER_SETTINGS_KEY id: number; // Only one entry, keyed with MASTER_SETTINGS_KEY
activeDid?: string; // Active Decentralized ID activeDid?: string; // Active Decentralized ID
apiServer?: string; // API server URL apiServer?: string; // API server URL
firstName?: string; // User's first name firstName?: string; // User's first name
lastName?: string; // User's last name
lastViewedClaimId?: string; // Last viewed claim ID
lastNotifiedClaimId?: string; // Last notified claim ID
isRegistered?: boolean; isRegistered?: boolean;
webPushServer?: string; // Web Push server URL lastName?: string; // deprecated - put all names in firstName
lastNotifiedClaimId?: string; // Last notified claim ID
lastViewedClaimId?: string; // Last viewed claim ID
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
reminderOn?: boolean; // Toggle to enable or disable reminders
// Array of named search boxes defined by bounding boxes // Array of named search boxes defined by bounding boxes
searchBoxes?: Array<{ searchBoxes?: Array<{
@@ -30,8 +32,9 @@ export type Settings = {
showContactGivesInline?: boolean; // Display contact inline or not showContactGivesInline?: boolean; // Display contact inline or not
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders warnIfProdServer?: boolean; // Warn if using a production server
reminderOn?: boolean; // Toggle to enable or disable reminders warnIfTestServer?: boolean; // Warn if using a testing server
webPushServer?: string; // Web Push server URL
}; };
/** /**

View File

@@ -39,7 +39,6 @@ const routes: Array<RouteRecordRaw> = [
name: "account", name: "account",
component: () => component: () =>
import(/* webpackChunkName: "account" */ "../views/AccountViewView.vue"), import(/* webpackChunkName: "account" */ "../views/AccountViewView.vue"),
beforeEnter: enterOrStart,
}, },
{ {
path: "/claim/:id?", path: "/claim/:id?",

View File

@@ -1,5 +1,7 @@
<template> <template>
<QuickNav selected="Profile"></QuickNav> <QuickNav selected="Profile"></QuickNav>
<TopMessage />
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<!-- Heading --> <!-- Heading -->
@@ -32,8 +34,24 @@
</span> </span>
</div> </div>
<!-- ID notice -->
<div
v-if="!activeDid"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
>
<p class="mb-4">
<b>Note:</b> Before you can take any action, you need an ID.
</p>
<router-link
:to="{ name: 'start' }"
class="inline-block text-md uppercase bg-amber-600 text-white px-4 py-2 rounded-md"
>
Generate Identity
</router-link>
</div>
<!-- Registration notice --> <!-- Registration notice -->
<!-- We won't show any loading indicator; we'll just pop the message in once we know they need it. --> <!-- 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 <div
v-if="!loadingLimits && !limits?.nextWeekBeginDateTime" v-if="!loadingLimits && !limits?.nextWeekBeginDateTime"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4" class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
@@ -58,9 +76,9 @@
<span v-else> <span v-else>
<router-link <router-link
:to="{ name: 'new-edit-account' }" :to="{ name: 'new-edit-account' }"
class="text-xs bg-blue-500 text-white px-1.5 py-1 rounded-md" class="block w-full text-center text-md text-slate-500 uppercase border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
> >
(set name) (Set Your Name)
</router-link> </router-link>
</span> </span>
@@ -88,16 +106,25 @@
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
<label <label
v-if="notificationUnchanged"
for="toggleNotifications" for="toggleNotifications"
class="flex items-center cursor-pointer" class="flex items-center justify-between cursor-pointer"
@click=" @click="
this.$notify( !toggleNotifications
? this.$notify(
{ {
group: 'modal', group: 'modal',
type: 'notification-permission', type: 'notification-permission',
}, },
-1, -1,
) )
: this.$notify(
{
group: 'modal',
type: 'notification-off',
},
-1,
)
" "
> >
<!-- label --> <!-- label -->
@@ -119,37 +146,9 @@
></div> ></div>
</div> </div>
</label> </label>
<label <label v-else>
for="toggleMuteNotifications" Notification status may have changed. Revisit this page to see the
class="flex items-center cursor-pointer mt-4" latest setting.
@click="
this.$notify(
{
group: 'modal',
type: 'notification-mute',
},
-1,
)
"
>
<!-- label -->
<div>Mute Notifications</div>
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input
type="checkbox"
name="toggleMuteNotifications"
class="sr-only"
disabled
/>
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
</label> </label>
</div> </div>
@@ -157,7 +156,7 @@
<router-link <router-link
:to="{ name: 'seed-backup' }" :to="{ name: 'seed-backup' }"
href="" 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" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
> >
Backup Identifier Seed Backup Identifier Seed
@@ -172,30 +171,42 @@
</a> </a>
<a ref="downloadLink" /> <a ref="downloadLink" />
<div v-if="activeDid" class="flex py-2"> <div v-if="activeDid" class="my-8">
<button class="text-center text-md text-blue-500" @click="checkLimits()"> <h3 class="text-sm uppercase font-semibold mb-3">Rate Limits</h3>
<button
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
@click="checkLimits()"
>
Check Limits Check Limits
</button> </button>
<!-- show spinner if loading limits --> <!-- show spinner if loading limits -->
<div v-if="loadingLimits" class="ml-2"> <div v-if="loadingLimits" class="text-center mb-4">
Checking... <fa icon="spinner" class="fa-spin"></fa> Checking&hellip; <fa icon="spinner" class="fa-spin"></fa>
</div> </div>
<div class="ml-2"> <div class="mb-4">
{{ limitsMessage }} {{ limitsMessage }}
</div> </div>
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9"> <div v-if="!!limits?.nextWeekBeginDateTime">
<span class="font-bold">Rate Limits</span> <p class="mb-3 text-sm">
<p> You have done <b>{{ limits.doneClaimsThisWeek }}</b> claims out of
You have done {{ limits.doneClaimsThisWeek }} claims out of <b>{{ limits.maxClaimsPerWeek }}</b> for this week. Your claims
{{ limits.maxClaimsPerWeek }} for this week. Your claims counter counter resets at
resets at {{ readableTime(limits.nextWeekBeginDateTime) }} <b class="whitespace-nowrap">{{
readableTime(limits.nextWeekBeginDateTime)
}}</b>
</p> </p>
<p> <p class="text-sm">
You have done {{ limits.doneRegistrationsThisMonth }} registrations You have done
out of {{ limits.maxRegistrationsPerMonth }} for this month. (You can <b>{{ limits.doneRegistrationsThisMonth }}</b> registrations out of
register nobody on your first day, and after that only one a day in <b>{{ limits.maxRegistrationsPerMonth }}</b> for this month.
your first month.) Your registration counter resets at <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) }} {{ readableTime(limits.nextMonthBeginDateTime) }}
</b>
</p> </p>
</div> </div>
</div> </div>
@@ -209,13 +220,13 @@
Advanced Advanced
</h3> </h3>
<div v-if="showAdvanced"> <div v-if="showAdvanced">
<p> <p class="text-rose-600 mb-8">
Beware: the features here can be confusing and even change data in ways Beware: the features here can be confusing and even change data in ways
you do not expect. But we support your freedoms! you do not expect. But we support your freedom!
</p> </p>
<!-- Deep Identity Details --> <!-- Deep Identity Details -->
<h2 class="text-slate-500 text-sm font-bold mb-2 py-2"> <h2 class="text-sm uppercase font-semibold mb-3">
Deep Identity Details Deep Identity Details
</h2> </h2>
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
@@ -273,13 +284,11 @@
<label <label
for="toggleShowAmounts" for="toggleShowAmounts"
class="flex items-center cursor-pointer py-2" class="flex items-center justify-between cursor-pointer my-4"
@click="handleChange" @click="handleChange"
> >
<!-- label --> <!-- label -->
<h2 class="text-slate-500 text-sm font-bold mb-2"> <h2>Show amounts given with contacts</h2>
Show amounts given with contacts
</h2>
<!-- toggle --> <!-- toggle -->
<div class="relative ml-2"> <div class="relative ml-2">
<!-- input --> <!-- input -->
@@ -298,26 +307,28 @@
</div> </div>
</label> </label>
<div class="flex py-2"> <router-link
<button class="text-blue-500"> :to="{ name: 'statistics' }"
<router-link :to="{ name: 'statistics' }" class="block text-center"> class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
See Global Animated History of Giving See Global Animated History of Giving
</router-link> </router-link>
</button>
</div>
<div class="flex py-2">
<button class="text-blue-500">
<!-- id used by puppeteer test script --> <!-- id used by puppeteer test script -->
<router-link <router-link
id="switch-identity-link" id="switch-identity-link"
:to="{ name: 'identity-switcher' }" :to="{ name: 'identity-switcher' }"
class="block text-center" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
> >
Switch Identity / No Identity Switch Identity
</router-link> </router-link>
<button
@click="alertWebPushSubscription()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
Show Subscription from Web Push Server
</button> </button>
</div>
<div class="flex py-4"> <div class="flex py-4">
<h2 class="text-slate-500 text-sm font-bold mb-2">Claim Server</h2> <h2 class="text-slate-500 text-sm font-bold mb-2">Claim Server</h2>
@@ -353,6 +364,46 @@
</button> </button>
</div> </div>
<label
for="toggleProdWarningMessage"
class="flex items-center justify-between cursor-pointer my-4"
@click="toggleProdWarning"
>
<!-- label -->
<h2>Show warning if on prod server</h2>
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input type="checkbox" v-model="warnIfProdServer" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
</label>
<label
for="toggleTestWarningMessage"
class="flex items-center justify-between cursor-pointer my-4"
@click="toggleTestWarning"
>
<!-- label -->
<h2>Show warning if on test server</h2>
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input type="checkbox" v-model="warnIfTestServer" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
</label>
<div class="flex py-4"> <div class="flex py-4">
<h2 class="text-slate-500 text-sm font-bold mb-2"> <h2 class="text-slate-500 text-sm font-bold mb-2">
Notification Push Server Notification Push Server
@@ -404,6 +455,7 @@ import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { AppString } from "@/constants/app"; import { AppString } from "@/constants/app";
import { db, accountsDB } from "@/db/index"; import { db, accountsDB } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
@@ -428,7 +480,7 @@ interface IAccount {
derivationPath: string; derivationPath: string;
} }
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav, TopMessage } })
export default class AccountViewView extends Vue { export default class AccountViewView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;
@@ -440,6 +492,7 @@ export default class AccountViewView extends Vue {
derivationPath = ""; derivationPath = "";
givenName = ""; givenName = "";
isRegistered = false; isRegistered = false;
notificationUnchanged = true;
numAccounts = 0; numAccounts = 0;
publicHex = ""; publicHex = "";
publicBase64 = ""; publicBase64 = "";
@@ -457,14 +510,73 @@ export default class AccountViewView extends Vue {
showAdvanced = false; showAdvanced = false;
private isSubscribed = false; subscription: PushSubscription | null = null;
warnIfProdServer = false;
warnIfTestServer = false;
private isSubscribed = false;
get toggleNotifications() { get toggleNotifications() {
return this.isSubscribed; return this.isSubscribed;
} }
set toggleNotifications(value) { set toggleNotifications(value) {
this.isSubscribed = value; this.isSubscribed = value;
this.notificationUnchanged = false;
}
/**
* Async function executed when the component is created.
* Initializes the component's state with values from the database,
* handles identity-related tasks, and checks limitations.
*
* @throws Will display specific messages to the user based on different errors.
*/
async created() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
// Initialize component state with values from the database or defaults
this.initializeState(settings);
// Get and process the identity
const identity = await this.getIdentity(this.activeDid);
if (identity) {
this.processIdentity(identity);
}
} catch (err: unknown) {
this.handleError(err);
}
}
async mounted() {
try {
const registration = await navigator.serviceWorker.ready;
this.subscription = await registration.pushManager.getSubscription();
this.toggleNotifications = !!this.subscription;
} catch (error) {
console.error("Mount error:", error);
this.toggleNotifications = false;
}
}
/**
* Initializes component state with values from the database or defaults.
* @param {SettingsType} settings - Object containing settings from the database.
*/
initializeState(settings: Settings | undefined) {
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
this.apiServerInput = (settings?.apiServer as string) || "";
this.givenName =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
this.isRegistered = !!settings?.isRegistered;
this.showContactGives = !!settings?.showContactGivesInline;
this.warnIfProdServer = !!settings?.warnIfProdServer;
this.warnIfTestServer = !!settings?.warnIfTestServer;
this.webPushServer = (settings?.webPushServer as string) || "";
this.webPushServerInput = (settings?.webPushServer as string) || "";
} }
public async getIdentity(activeDid: string): Promise<IIdentifier | null> { public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
@@ -532,6 +644,16 @@ export default class AccountViewView extends Vue {
this.updateShowContactAmounts(); this.updateShowContactAmounts();
} }
toggleProdWarning() {
this.warnIfProdServer = !this.warnIfProdServer;
this.updateWarnIfProdServer(this.warnIfProdServer);
}
toggleTestWarning() {
this.warnIfTestServer = !this.warnIfTestServer;
this.updateWarnIfTestServer(this.warnIfTestServer);
}
readableTime(timeStr: string) { readableTime(timeStr: string) {
return timeStr.substring(0, timeStr.indexOf("T")); return timeStr.substring(0, timeStr.indexOf("T"));
} }
@@ -541,62 +663,6 @@ export default class AccountViewView extends Vue {
this.numAccounts = await accountsDB.accounts.count(); this.numAccounts = await accountsDB.accounts.count();
} }
/**
* Async function executed when the component is created.
* Initializes the component's state with values from the database,
* handles identity-related tasks, and checks limitations.
*
* @throws Will display specific messages to the user based on different errors.
*/
async created() {
console.error("created");
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
// Initialize component state with values from the database or defaults
this.initializeState(settings);
// Get and process the identity
const identity = await this.getIdentity(this.activeDid);
if (identity) {
this.processIdentity(identity);
}
} catch (err: unknown) {
this.handleError(err);
}
}
async mounted() {
console.error("mounted()");
try {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.getSubscription();
this.toggleNotifications = !!subscription;
} catch (error) {
console.error(error);
this.toggleNotifications = false;
}
}
/**
* Initializes component state with values from the database or defaults.
* @param {SettingsType} settings - Object containing settings from the database.
*/
initializeState(settings: Settings | undefined) {
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
this.apiServerInput = (settings?.apiServer as string) || "";
this.givenName =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
this.isRegistered = !!settings?.isRegistered;
this.webPushServer = (settings?.webPushServer as string) || "";
this.webPushServerInput = (settings?.webPushServer as string) || "";
this.showContactGives = !!settings?.showContactGivesInline;
}
/** /**
* Processes the identity and updates the component's state. * Processes the identity and updates the component's state.
* @param {IdentityType} identity - Object containing identity information. * @param {IdentityType} identity - Object containing identity information.
@@ -670,6 +736,52 @@ export default class AccountViewView extends Vue {
} }
} }
public async updateWarnIfProdServer(newSetting: boolean) {
try {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
warnIfProdServer: newSetting,
});
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating Prod Warning",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
console.error(
"Telling user to clear cache after contact setting update because:",
err,
);
}
}
public async updateWarnIfTestServer(newSetting: boolean) {
try {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
warnIfTestServer: newSetting,
});
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating Test Warning",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
console.error(
"Telling user to clear cache after contact setting update because:",
err,
);
}
}
/** /**
* Asynchronously exports the database into a downloadable JSON file. * Asynchronously exports the database into a downloadable JSON file.
* *
@@ -927,6 +1039,23 @@ export default class AccountViewView extends Vue {
webPushServer: this.webPushServerInput, webPushServer: this.webPushServerInput,
}); });
this.webPushServer = this.webPushServerInput; this.webPushServer = this.webPushServerInput;
this.$notify(
{
group: "alert",
type: "warning",
title: "Reload",
text: "Now reload the app to get a new VAPID to use with this push server.",
},
-1,
);
}
alertWebPushSubscription() {
console.log(
"Web push subscription:",
JSON.parse(JSON.stringify(this.subscription)), // gives more info than plain console logging
);
alert(JSON.stringify(this.subscription));
} }
} }
</script> </script>

View File

@@ -19,9 +19,9 @@
<!-- Details --> <!-- Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<div> <div>
<div class="block pb-4 flex gap-4 overflow-hidden"> <div class="block flex gap-4 overflow-hidden">
<div class="overflow-hidden"> <div class="overflow-hidden">
<h2 class="text-xl">{{ veriClaim.id }}</h2> <h2 class="text-md font-bold">{{ veriClaim.id }}</h2>
<div class="text-sm"> <div class="text-sm">
<div> <div>
{{ veriClaim.claimType }} {{ veriClaim.claimType }}
@@ -45,7 +45,7 @@
</div> </div>
<div> <div>
<h2 class="font-bold text-2xl">Confirmations</h2> <h2 class="font-bold uppercase text-xl mt-8 mb-2">Confirmations</h2>
<span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span> <span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span>
<span v-else-if="totalConfirmers() === 1"> <span v-else-if="totalConfirmers() === 1">
@@ -136,22 +136,24 @@
</div> </div>
<div> <div>
<h2 class="font-bold text-2xl mt-8">Claim</h2> <h2 class="font-bold uppercase text-xl mt-8 mb-2">Claim</h2>
<pre>{{ util.inspect(veriClaim, false, null) }}</pre> <pre class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md">
{{ util.inspect(veriClaim, false, null) }}
</pre>
</div> </div>
<h2 class="font-bold text-2xl mt-8">Full Claim</h2> <h2 class="font-bold uppercase text-xl mt-8 mb-2">Full Claim</h2>
<p> <p class="mb-4">
The full claim includes the claim as it was originally issued, including The full claim includes the claim as it was originally issued, including
the signature (ie. the proof of issuance by that person). the signature (ie. the proof of issuance by that person).
</p> </p>
<div v-if="!fullClaim"> <div v-if="!fullClaim">
<div v-if="fullClaimMessage"> <p v-if="fullClaimMessage" class="mb-4">
{{ fullClaimMessage }} {{ fullClaimMessage }}
</div> </p>
<button <button
v-else v-else
class="bg-blue-600 text-white mt-4 px-4 py-2 rounded-md mb-4" class="block w-full text-center text-md uppercase bg-blue-600 text-white px-1.5 py-2 rounded-md mb-2"
@click="showFullClaim(veriClaim.id)" @click="showFullClaim(veriClaim.id)"
> >
Load Full Claim Details Load Full Claim Details
@@ -161,10 +163,12 @@
<pre>{{ util.inspect(fullClaim, false, null) }}</pre> <pre>{{ util.inspect(fullClaim, false, null) }}</pre>
</div> </div>
<a :href="apiServer + '/api/claim/' + veriClaim.id" target="_blank"> <a
<button class="bg-blue-600 text-white mt-4 px-4 py-2 rounded-md mb-4"> :href="apiServer + '/api/claim/' + veriClaim.id"
target="_blank"
class="block w-full text-center text-md uppercase bg-blue-600 text-white px-1.5 py-2 rounded-md mb-2"
>
View on the Public Server View on the Public Server
</button>
</a> </a>
</section> </section>
</template> </template>

View File

@@ -124,7 +124,7 @@ interface Notification {
} }
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
export default class ContactsView extends Vue { export default class ContactAmountssView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;
activeDid = ""; activeDid = "";

View File

@@ -18,6 +18,16 @@
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4">
Your Contact Info Your Contact Info
</h1> </h1>
<p v-if="!givenName" class="text-center mt-2">
<span class="text-red">Beware!</span>
You aren't sharing your name, so hurry and
<router-link
:to="{ name: 'new-edit-account' }"
class="bg-blue-500 text-white px-1.5 py-1 rounded-md"
>
go here to set it for them.
</router-link>
</p>
</div> </div>
<div @click="onCopyToClipboard()" v-if="activeDid"> <div @click="onCopyToClipboard()" v-if="activeDid">
@@ -87,6 +97,7 @@ export default class ContactQRScanShow extends Vue {
activeDid = ""; activeDid = "";
apiServer = ""; apiServer = "";
givenName = "";
qrValue = ""; qrValue = "";
public async getIdentity(activeDid: string) { public async getIdentity(activeDid: string) {
@@ -111,6 +122,7 @@ export default class ContactQRScanShow extends Vue {
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = settings?.activeDid || ""; this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || ""; this.apiServer = settings?.apiServer || "";
this.givenName = settings?.firstName || "";
await accountsDB.open(); await accountsDB.open();
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();

View File

@@ -19,12 +19,13 @@
</div> </div>
<!-- New Contact --> <!-- New Contact -->
<div class="mb-4 flex"> <div class="mb-4 flex items-stretch">
<span class="self-center bg-slate-500 text-white px-1.5 py-1 rounded-md"> <router-link
<router-link :to="{ name: 'contact-qr' }"> :to="{ name: 'contact-qr' }"
<fa icon="qrcode" class="fa-fw" /> class="flex items-center bg-slate-500 text-white px-1.5 py-1 mr-1 rounded-md"
>
<fa icon="qrcode" class="fa-fw text-2xl" />
</router-link> </router-link>
</span>
<input <input
type="text" type="text"
placeholder="DID, Name, Public Key (base 16 or 64)" placeholder="DID, Name, Public Key (base 16 or 64)"
@@ -170,7 +171,7 @@
<button <button
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-l-md" class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-l-md"
@click="onClickAddGive(activeDid, contact.did)" @click="onClickAddGive(activeDid, contact.did)"
title="givenByMeDescriptions[contact.did]" :title="givenByMeDescriptions[contact.did] || ''"
> >
To: To:
{{ {{
@@ -189,7 +190,7 @@
<button <button
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-r-md -ml-1.5 border-l border-blue-400" class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-r-md -ml-1.5 border-l border-blue-400"
@click="onClickAddGive(contact.did, activeDid)" @click="onClickAddGive(contact.did, activeDid)"
title="givenToMeDescriptions[contact.did]" :title="givenToMeDescriptions[contact.did] || ''"
> >
From: From:
{{ {{
@@ -366,6 +367,10 @@ export default class ContactsView extends Vue {
} }
async loadGives() { async loadGives() {
if (!this.activeDid) {
return;
}
const handleResponse = ( const handleResponse = (
resp: { status: number; data: { data: GiveServerRecord[] } }, resp: { status: number; data: { data: GiveServerRecord[] } },
descriptions: Record<string, string>, descriptions: Record<string, string>,
@@ -400,11 +405,11 @@ export default class ContactsView extends Vue {
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Server Error", title: "Retrieval Error",
text: text:
"Got an error retrieving your " + "Got an error retrieving your " +
(useRecipient ? "given" : "received") + (useRecipient ? "given" : "received") +
" time from the server.", " data from the server.",
}, },
-1, -1,
); );
@@ -455,12 +460,13 @@ export default class ContactsView extends Vue {
this.givenToMeConfirmed = givenToMeConfirmed; this.givenToMeConfirmed = givenToMeConfirmed;
this.givenToMeUnconfirmed = givenToMeUnconfirmed; this.givenToMeUnconfirmed = givenToMeUnconfirmed;
} catch (error) { } catch (error) {
console.log("Error loading gives", error);
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Server Error", title: "Load Error",
text: error as string, text: "Got an error loading your gives.",
}, },
-1, -1,
); );
@@ -704,6 +710,7 @@ export default class ContactsView extends Vue {
); );
} }
} catch (error) { } catch (error) {
console.error("Error when registering:", error);
let userMessage = "There was an error. See logs for more info."; let userMessage = "There was an error. See logs for more info.";
const serverError = error as AxiosError; const serverError = error as AxiosError;
if (serverError) { if (serverError) {
@@ -720,7 +727,7 @@ export default class ContactsView extends Vue {
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Server Error", title: "Registration Error",
text: userMessage, text: userMessage,
}, },
-1, -1,
@@ -766,27 +773,28 @@ export default class ContactsView extends Vue {
} else { } else {
console.error( console.error(
"Got some bad server response when setting visibility: ", "Got some bad server response when setting visibility: ",
resp.status,
resp, resp,
); );
const message = const message =
resp.data.error?.message || "Bad server response of " + resp.status; resp.data.error?.message || "Got some error setting visibility.";
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Server Error", title: "Error Setting Visibility",
text: message, text: message,
}, },
-1, -1,
); );
} }
} catch (err) { } catch (err) {
console.error("Got some server error when setting visibility:", err); console.error("Got some error when setting visibility:", err);
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Server Error", title: "Error Setting Visibility",
text: "Check connectivity and try again.", text: "Check connectivity and try again.",
}, },
-1, -1,
@@ -829,19 +837,19 @@ export default class ContactsView extends Vue {
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Server Error", title: "Error Checking Visibility",
text: message, text: message,
}, },
-1, -1,
); );
} }
} catch (err) { } catch (err) {
console.log("Caught error from server request to check visibility:", err); console.log("Caught error from request to check visibility:", err);
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Server Error", title: "Error Checking Visibility",
text: "Check connectivity and try again.", text: "Check connectivity and try again.",
}, },
-1, -1,
@@ -1027,6 +1035,7 @@ export default class ContactsView extends Vue {
} }
} }
} catch (error) { } catch (error) {
console.log("Error in createAndSubmitGive: ", error);
let userMessage = "There was an error. See logs for more info."; let userMessage = "There was an error. See logs for more info.";
const serverError = error as AxiosError; const serverError = error as AxiosError;
if (serverError) { if (serverError) {
@@ -1043,7 +1052,7 @@ export default class ContactsView extends Vue {
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Server Error", title: "Error Sending Give",
text: userMessage, text: userMessage,
}, },
-1, -1,

View File

@@ -1,5 +1,6 @@
<template> <template>
<QuickNav selected="Discover"></QuickNav> <QuickNav selected="Discover"></QuickNav>
<TopMessage />
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
@@ -136,6 +137,7 @@ import { didInfo, ProjectData } from "@/libs/endorserServer";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import TopMessage from "@/components/TopMessage.vue";
interface Notification { interface Notification {
group: string; group: string;
@@ -149,6 +151,7 @@ interface Notification {
QuickNav, QuickNav,
InfiniteScroll, InfiniteScroll,
EntityIcon, EntityIcon,
TopMessage,
}, },
}) })
export default class DiscoverView extends Vue { export default class DiscoverView extends Vue {

View File

@@ -28,13 +28,35 @@
<p>Somehow call the service-worker self.showNotification</p> <p>Somehow call the service-worker self.showNotification</p>
<h2 class="text-xl font-semibold">Check OS-level permissions</h2> <h2 class="text-xl font-semibold">Check OS-level permissions</h2>
<p> <div>
Walk-throughs & screenshots, maybe for all combinations of OS & Walk-throughs & screenshots, maybe for all combinations of OS &
browsers. browsers.
</p> <div>
<h3 class="text-lg font-semibold">Mobile Phone - Apple iOS</h3>
<div>
Notifications require iOS 16.4 or higher. To check your iOS version,
go to Settings > General > About > Software Version.
</div>
<h3 class="text-lg font-semibold">Mobile Phone - Google Android</h3>
<div>See the browser section.</div>
<h3 class="text-lg font-semibold">Desktop - Mac</h3>
<div>Requires Mac OS 13.</div>
</div>
</div>
<h2 class="text-xl font-semibold">Check browser-level permissions</h2> <h2 class="text-xl font-semibold">Check browser-level permissions</h2>
<p>Walk-throughs & screenshots for browser settings</p> <p>Walk-throughs & screenshots for browser settings</p>
<div>
<div>
<h3 class="text-lg font-semibold">Mobile Phone - Apple iOS</h3>
<div>Make sure your OS (above) supports it.</div>
<h3 class="text-lg font-semibold">Mobile Phone - Android</h3>
<div>Chrome requires version 50.</div>
</div>
</div>
<h2 class="text-xl font-semibold">Explain full reset to start again</h2> <h2 class="text-xl font-semibold">Explain full reset to start again</h2>
<p> <p>

View File

@@ -214,9 +214,7 @@
</p> </p>
<h2 class="text-xl font-semibold">What app version is this?</h2> <h2 class="text-xl font-semibold">What app version is this?</h2>
<p> <p>{{ package.version }} ({{ commitHash }})</p>
{{ package.version }}
</p>
<h2 class="text-xl font-semibold"> <h2 class="text-xl font-semibold">
For any other questions, including removing your data: For any other questions, including removing your data:
@@ -246,6 +244,7 @@ export default class Help extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;
package = Package; package = Package;
commitHash = process.env.VUE_APP_GIT_HASH;
showOnboardInfo() { showOnboardInfo() {
this.$notify( this.$notify(

View File

@@ -1,5 +1,7 @@
<template> <template>
<QuickNav selected="Home"></QuickNav> <QuickNav selected="Home"></QuickNav>
<TopMessage />
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
@@ -8,28 +10,44 @@
<!-- show the actions for recognizing a give --> <!-- show the actions for recognizing a give -->
<div class="mb-8"> <div class="mb-8">
<div v-if="!activeDid"> <div
To record others' giving, v-if="!activeDid"
<router-link :to="{ name: 'start' }" class="text-blue-500"> class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
create your identifier.</router-link >
<p class="text-lg mb-3">
You need an <b>identifier</b> before you can record anyone's gives.
</p>
<router-link
:to="{ name: 'start' }"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
>
Create Your Identifier</router-link
> >
</div> </div>
<div v-else-if="!isRegistered"> <div
To record others' giving, someone must register your account, so show v-else-if="!isRegistered"
them class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
<router-link :to="{ name: 'contact-qr' }" class="text-blue-500">
your identity info</router-link
> >
and then Someone must register your account before you can record anyone's gives.
<router-link :to="{ name: 'account' }" class="text-blue-500"> To do this:
check your limits.</router-link <router-link
:to="{ name: 'contact-qr' }"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
>
1. Show Them Your Identity Info</router-link
>
<router-link
:to="{ name: 'account' }"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
>
2. Check Your Limits</router-link
> >
</div> </div>
<div v-else> <div v-else>
<!-- activeDid && isRegistered --> <!-- activeDid && isRegistered -->
<h2 class="text-xl font-bold">Record Something Given</h2> <h2 class="text-xl font-bold mb-4">Record Something Given</h2>
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5"> <ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
<li @click="openDialog()"> <li @click="openDialog()">
@@ -87,13 +105,10 @@
showGivenToUser="true" showGivenToUser="true"
/> />
<!-- Results List -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<h2 class="text-xl font-bold mb-4">Latest Activity</h2> <h2 class="text-xl font-bold mb-4">Latest Activity</h2>
<div :class="{ hidden: isHiddenSpinner }"> <InfiniteScroll @reached-bottom="loadMoreGives">
<p class="text-slate-500 text-center italic mt-4 mb-4">
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
</p>
</div>
<ul class="border-t border-slate-300"> <ul class="border-t border-slate-300">
<li <li
class="border-b border-slate-300 py-2" class="border-b border-slate-300 py-2"
@@ -102,10 +117,11 @@
> >
<div <div
class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm" class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm"
v-if="record.jwtId == feedLastViewedId" v-if="record.jwtId == feedLastViewedClaimId"
> >
You've seen all the following You've seen all the following
</div> </div>
<div class="flex"> <div class="flex">
<fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa> <fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa>
<span class="">{{ this.giveDescription(record) }}</span> <span class="">{{ this.giveDescription(record) }}</span>
@@ -115,14 +131,27 @@
</div> </div>
</li> </li>
</ul> </ul>
</InfiniteScroll>
<div :class="{ hidden: isHiddenSpinner }">
<p class="text-slate-500 text-center italic mt-4 mb-4">
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
</p>
</div>
</div> </div>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import EntityIcon from "@/components/EntityIcon.vue";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { db, accountsDB } from "@/db/index"; import { db, accountsDB } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import { import {
@@ -130,11 +159,7 @@ import {
GiverInputInfo, GiverInputInfo,
GiveServerRecord, GiveServerRecord,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import { Contact } from "@/db/tables/contacts";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import { Account } from "@/db/tables/accounts";
interface Notification { interface Notification {
group: string; group: string;
@@ -144,7 +169,13 @@ interface Notification {
} }
@Component({ @Component({
components: { GiftedDialog, QuickNav, EntityIcon }, components: {
GiftedDialog,
QuickNav,
EntityIcon,
InfiniteScroll,
TopMessage,
},
}) })
export default class HomeView extends Vue { export default class HomeView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;
@@ -153,10 +184,9 @@ export default class HomeView extends Vue {
allContacts: Array<Contact> = []; allContacts: Array<Contact> = [];
allMyDids: Array<string> = []; allMyDids: Array<string> = [];
apiServer = ""; apiServer = "";
feedAllLoaded = false;
feedData = []; feedData = [];
feedPreviousOldestId?: string; feedPreviousOldestId?: string;
feedLastViewedId?: string; feedLastViewedClaimId?: string;
isHiddenSpinner = true; isHiddenSpinner = true;
isRegistered = false; isRegistered = false;
numAccounts = 0; numAccounts = 0;
@@ -196,9 +226,12 @@ export default class HomeView extends Vue {
this.apiServer = settings?.apiServer || ""; this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || ""; this.activeDid = settings?.activeDid || "";
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
this.feedLastViewedId = settings?.lastViewedClaimId; this.feedLastViewedClaimId = settings?.lastViewedClaimId;
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings?.isRegistered;
// this returns a Promise but we don't need to wait for it
this.updateAllFeed(); this.updateAllFeed();
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) { } catch (err: any) {
this.$notify( this.$notify(
@@ -241,24 +274,33 @@ export default class HomeView extends Vue {
return headers; return headers;
} }
/**
* Data loader used by infinite scroller
* @param payload is the flag from the InfiniteScroll indicating if it should load
**/
public async loadMoreGives(payload: boolean) {
if (payload) {
this.updateAllFeed();
}
}
public async updateAllFeed() { public async updateAllFeed() {
this.isHiddenSpinner = false; this.isHiddenSpinner = false;
await this.retrieveClaims(this.apiServer, this.feedPreviousOldestId) await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
.then(async (results) => { .then(async (results) => {
if (results.data.length > 0) { if (results.data.length > 0) {
this.feedData = this.feedData.concat(results.data); this.feedData = this.feedData.concat(results.data);
this.feedAllLoaded = results.hitLimit;
this.feedPreviousOldestId = this.feedPreviousOldestId =
results.data[results.data.length - 1].jwtId; results.data[results.data.length - 1].jwtId;
// The following update is only done on the first load.
if ( if (
this.feedLastViewedId == null || this.feedLastViewedClaimId == null ||
this.feedLastViewedId < results.data[0].jwtId this.feedLastViewedClaimId < results.data[0].jwtId
) { ) {
await db.open(); await db.open();
db.settings.update(MASTER_SETTINGS_KEY, { db.settings.update(MASTER_SETTINGS_KEY, {
lastViewedClaimId: results.data[0].jwtId, lastViewedClaimId: results.data[0].jwtId,
}); });
// but not for this page because we need to remember what it was before
} }
} }
}) })
@@ -268,17 +310,22 @@ export default class HomeView extends Vue {
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Export Error", title: "Feed Error",
text: e.userMessage || "There was an error retrieving feed data.", text: e.userMessage || "There was an error retrieving feed data.",
}, },
-1, -1,
); );
}); });
this.isHiddenSpinner = true; this.isHiddenSpinner = true;
} }
public async retrieveClaims(endorserApiServer: string, beforeId?: string) { /**
* Retrieve claims in reverse chronological order
*
* @param beforeId the earliest ID (of previous searches) to search earlier
* @return claims in reverse chronological order
*/
public async retrieveGives(endorserApiServer: string, beforeId?: string) {
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId; const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
const response = await fetch( const response = await fetch(
endorserApiServer + "/api/v2/report/gives?" + beforeQuery, endorserApiServer + "/api/v2/report/gives?" + beforeQuery,

View File

@@ -68,7 +68,7 @@ export default class NewEditAccountView extends Vue {
}); });
localStorage.setItem("firstName", this.givenName as string); localStorage.setItem("firstName", this.givenName as string);
localStorage.setItem("lastName", ""); // deprecated, pre v 0.1.3 localStorage.setItem("lastName", ""); // deprecated, pre v 0.1.3
this.$router.push({ name: "account" }); this.$router.back();
} }
onClickCancel() { onClickCancel() {

View File

@@ -37,7 +37,7 @@
maxlength="5000" maxlength="5000"
></textarea> ></textarea>
<div class="text-xs text-slate-500 italic -mt-3 mb-4"> <div class="text-xs text-slate-500 italic -mt-3 mb-4">
{{ fullClaim.description.length }}/5000 max. characters {{ fullClaim.description?.length }}/5000 max. characters
</div> </div>
<input <input

View File

@@ -88,7 +88,7 @@ export default class NewIdentifierView extends Vue {
this.loading = false; this.loading = false;
setTimeout(() => { setTimeout(() => {
this.$router.push({ name: "account" }); this.$router.push({ name: "home" });
}, 1000); }, 1000);
} }
} }

View File

@@ -1,5 +1,7 @@
<template> <template>
<QuickNav /> <QuickNav />
<TopMessage />
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<!-- Breadcrumb --> <!-- Breadcrumb -->
@@ -88,8 +90,8 @@
</button> </button>
</div> </div>
<div class="mb-4"> <div v-if="activeDid" class="mb-4">
<div v-if="activeDid" class="text-center"> <div class="text-center">
<button <button
@click="openOfferDialog({ name: 'you', did: activeDid })" @click="openOfferDialog({ name: 'you', did: activeDid })"
class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md" class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
@@ -99,17 +101,16 @@
</div> </div>
</div> </div>
<div> <div v-if="activeDid">
<div v-if="activeDid" class="text-center"> <div class="text-center">
<button <button
@click="openGiftDialog({ name: 'you', did: activeDid })" @click="openGiftDialog({ name: 'you', did: activeDid })"
class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md" class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
> >
I gave&hellip; I gave&hellip;
</button> </button>
<p class="mt-2 mb-4 text-center">Or, record a gift from:</p> <p class="mt-2 mb-4 text-center">Or, record a contribution from:</p>
</div> </div>
<p v-if="!activeDid" class="mt-2 mb-4">Record a gift from:</p>
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5"> <ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
<li @click="openGiftDialog()"> <li @click="openGiftDialog()">
@@ -234,22 +235,25 @@
<h3 class="text-sm uppercase font-semibold mb-3"> <h3 class="text-sm uppercase font-semibold mb-3">
Contributions To This Idea Contributions To This Idea
</h3> </h3>
<ul> <!-- centering because long, wrapped project names didn't left align with blank or "text-left" -->
<li v-for="plan in fulfillersToThis" :key="plan.handleId"> <div class="text-center">
<div v-for="plan in fulfillersToThis" :key="plan.handleId">
<button <button
@click="onClickLoadProject(plan.handleId)" @click="onClickLoadProject(plan.handleId)"
class="text-blue-500" class="text-blue-500"
> >
{{ plan.name }} {{ plan.name }}
</button> </button>
</li> </div>
</ul> </div>
</div> </div>
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md"> <div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
<h3 class="text-sm uppercase font-semibold mb-3"> <h3 class="text-sm uppercase font-semibold mb-3">
Contributions By This Idea Contributions By This Idea
</h3> </h3>
<!-- centering because long, wrapped project names didn't left align with blank or "text-left" -->
<div class="text-center">
<button <button
@click="onClickLoadProject(fulfilledByThis.handleId)" @click="onClickLoadProject(fulfilledByThis.handleId)"
class="text-blue-500" class="text-blue-500"
@@ -259,6 +263,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<GiftedDialog <GiftedDialog
ref="customGiveDialog" ref="customGiveDialog"
@@ -279,6 +284,7 @@ import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import OfferDialog from "@/components/OfferDialog.vue"; import OfferDialog from "@/components/OfferDialog.vue";
import TopMessage from "@/components/TopMessage.vue";
import { accountsDB, db } from "@/db/index"; import { accountsDB, db } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
@@ -303,7 +309,7 @@ interface Notification {
} }
@Component({ @Component({
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav }, components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav, TopMessage },
}) })
export default class ProjectViewView extends Vue { export default class ProjectViewView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;
@@ -356,12 +362,6 @@ export default class ProjectViewView extends Vue {
.equals(activeDid) .equals(activeDid)
.first()) as Account; .first()) as Account;
const identity = JSON.parse(account?.identity || "null"); const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"Attempted to load project records with no identity available.",
);
}
return identity; return identity;
} }

View File

@@ -1,9 +1,11 @@
<template> <template>
<QuickNav selected="Projects"></QuickNav> <QuickNav selected="Projects"></QuickNav>
<TopMessage />
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<!-- Heading --> <!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Your Plans Your Ideas
</h1> </h1>
<!-- Quick Search --> <!-- Quick Search -->
@@ -79,6 +81,7 @@ import { IIdentifier } from "@veramo/core";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import TopMessage from "@/components/TopMessage.vue";
import { ProjectData } from "@/libs/endorserServer"; import { ProjectData } from "@/libs/endorserServer";
interface Notification { interface Notification {
@@ -89,7 +92,7 @@ interface Notification {
} }
@Component({ @Component({
components: { InfiniteScroll, QuickNav, EntityIcon }, components: { InfiniteScroll, QuickNav, EntityIcon, TopMessage },
}) })
export default class ProjectsView extends Vue { export default class ProjectsView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;

View File

@@ -5,7 +5,7 @@ importScripts(
); );
self.addEventListener("install", (event) => { self.addEventListener("install", (event) => {
console.error("Adding event listener for:", event); console.log("Adding event listener for:", event);
importScripts( importScripts(
"safari-notifications.js", "safari-notifications.js",
"nacl.js", "nacl.js",

View File

@@ -395,12 +395,12 @@ async function setMostRecentNotified(id) {
data["lastNotifiedClaimId"] = id; data["lastNotifiedClaimId"] = id;
await updateRecord(store, data); await updateRecord(store, data);
} else { } else {
console.error("Record not found"); console.error("IndexedDB settings record not found.");
} }
transaction.oncomplete = () => db.close(); transaction.oncomplete = () => db.close();
} catch (error) { } catch (error) {
console.error("Database error: " + error.message); console.error("IndexedDB error:", error);
} }
} }
@@ -520,7 +520,11 @@ async function getNotificationCount() {
const most_recent_notified = claims[0]["id"]; const most_recent_notified = claims[0]["id"];
await setMostRecentNotified(most_recent_notified); await setMostRecentNotified(most_recent_notified);
} else { } else {
console.error("The service worker got a bad response status when fetching claims:", response.status, response); console.error(
"The service worker got a bad response status when fetching claims:",
response.status,
response,
);
} }
break; break;
} }

View File

@@ -1,4 +1,8 @@
const { defineConfig } = require("@vue/cli-service"); const { defineConfig } = require("@vue/cli-service");
const { gitDescribeSync } = require("git-describe");
process.env.VUE_APP_GIT_HASH = gitDescribeSync().hash;
module.exports = defineConfig({ module.exports = defineConfig({
transpileDependencies: true, transpileDependencies: true,
configureWebpack: { configureWebpack: {

View File

@@ -400,3 +400,22 @@ While notifications are turned on, the user can tap on the App Notifications tog
* Active. (User can change to Muted when the user mutes notifications.) * Active. (User can change to Muted when the user mutes notifications.)
* Muted. (User can change to Active when the user toggles it.) * Muted. (User can change to Active when the user toggles it.)
(Turning mute off automatically after some amount of time is not planned in version 1.) (Turning mute off automatically after some amount of time is not planned in version 1.)
# TROUBLESHOOTING
## Desktop
#### Firefox
Go to `about:debugging` and click on `Inspect` for the service worker.
#### Chrome
Go to `chrome://inspect/#service-workers` and click on `Inspect` for the service worker.
## Mobile
#### Android
#### iOS