diff --git a/README.md b/README.md index b27c591..f23c321 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,10 @@ Under the "Your Identity" screen, click "Advanced", click "Switch Identity / No For your own web-push tests, change the 'vapid' URL in App.vue, and install apps on the same domain. +### Icons + +To add an icon, add to main.ts and reference with `fa` element and `icon` attribute with the hyphenated name. + ### Manual walk-through - Clear the browser cache for localhost for a new user. diff --git a/project.task.yaml b/project.task.yaml index f36b06f..40ff1fb 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -6,19 +6,40 @@ tasks: - extract private_key_hex in py-push-server webpush.py - lock down regenerate_vapid endpoint (so only we admins can do it on demand) - remove sleep in py-push-server app.py + - revisit "maybe" and "never" buttons on accont screen + - see if we can detect OS-level notifications if turned off + - write troubleshooting docs for notifications - .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 - .5 Add infinite scroll to gifts on the home page -- Discuss whether the remaining tasks are worthwhile before MVP release. +- .5 If notifications are not enabled, add message to front page with link/button to enable -- .1 Add units or different icon to the coins (to distinguish $, BTC, hours, etc) -- .5 make a VC details page, or link to endorser.ch (including confirmations) -- 01 allow download of each VC (& confirmations, to show that they actually own their data) +- show VC details... somehow: + - .5 make a VC details page, or link to endorser.ch (including confirmations) + - 01 allow download of each VC (& confirmations, to show that they actually own their data) + - 04 allow user to download VCs, mine + ones I can see about me from others + - add VC confirmation? + +- 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) + - 08 thorough testing for errors & edge cases + - 01 ensure ability to recover server remotely, and add redundant access + - Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot). + - Add disclaimers. + - Switch default server to the public server. + - Deploy to a server. + - Ensure public server has limits that work for group adoption. + - Test PWA features on Android and iOS. + blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time + +- make identicons for contacts into more-memorable faces (and maybe change project identicons, too) +- allow some gives even if they aren't registered +- .5 Add start date to project - .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? -- 04 allow user to download claims, mine + ones I can see about me from others - .5 customize favicon assignee-group:ui - .2 Show a warning if both giver and recipient are the same (but still allow?) - 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui @@ -26,8 +47,6 @@ tasks: - .5 include the hash of the latest commit on help page next to version (maybe Trent's git-hash branch) - .5 remove references to localStorage for projectId (now that it's pulling from the path) - bug (that is hard to reproduce) - on the second 'give' recorded on prod it showed me as the agent -- make identicons for contacts into more-memorable faces (and maybe change project identicons, too) -- allow some gives even if they aren't registered - 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" - .5 fix cert generation on server (since it didn't happen automatically for Nov 30) @@ -43,19 +62,6 @@ tasks: - maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version") - 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie) -- Release Minimum Viable Product : - - generate new webpush.db entries, data/webpush.db 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) - - 08 thorough testing for errors & edge cases - - 01 ensure ability to recover server remotely, and add redundant access - - Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot). - - Add disclaimers. - - Switch default server to the public server. - - Deploy to a server. - - Ensure public server has limits that work for group adoption. - - Test PWA features on Android and iOS. - blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time - - .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 diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 0c92fbf..3a9033f 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -69,6 +69,8 @@ export interface OfferServerRecord { validThrough: string; } +// Note that previous VCs may have additional fields. +// https://endorser.ch/doc/html/transactions.html#id4 export interface GiveVerifiableCredential { "@context"?: string; // optional when embedded, eg. in an Agree "@type": "GiveAction"; @@ -80,6 +82,8 @@ export interface GiveVerifiableCredential { recipient?: { identifier: string }; } +// Note that previous VCs may have additional fields. +// https://endorser.ch/doc/html/transactions.html#id8 export interface OfferVerifiableCredential { "@context"?: string; // optional when embedded, eg. in an Agree "@type": "Offer"; @@ -93,6 +97,8 @@ export interface OfferVerifiableCredential { validThrough?: string; } +// Note that previous VCs may have additional fields. +// https://endorser.ch/doc/html/transactions.html#id7 export interface PlanVerifiableCredential { "@context": "https://schema.org"; "@type": "PlanAction"; diff --git a/src/libs/util.ts b/src/libs/util.ts new file mode 100644 index 0000000..693a331 --- /dev/null +++ b/src/libs/util.ts @@ -0,0 +1,3 @@ +export const isGlobalUri = (uri: string) => { + return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/)); +}; diff --git a/src/main.ts b/src/main.ts index 7bcaade..997d572 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,6 +14,7 @@ import { faArrowLeft, faArrowRight, faBan, + faBitcoinSign, faBurst, faCalendar, faChevronLeft, @@ -27,6 +28,7 @@ import { faCoins, faComment, faCopy, + faDollar, faEllipsisVertical, faEye, faEyeSlash, @@ -34,6 +36,7 @@ import { faFloppyDisk, faFolderOpen, faGift, + faGlobe, faHand, faHouseChimney, faLocationDot, @@ -44,6 +47,7 @@ import { faPersonCircleCheck, faPersonCircleQuestion, faPlus, + faQuestion, faQrcode, faRotate, faShareNodes, @@ -61,6 +65,7 @@ library.add( faArrowLeft, faArrowRight, faBan, + faBitcoinSign, faBurst, faCalendar, faChevronLeft, @@ -74,6 +79,7 @@ library.add( faCoins, faComment, faCopy, + faDollar, faEllipsisVertical, faEye, faEyeSlash, @@ -81,6 +87,7 @@ library.add( faFloppyDisk, faFolderOpen, faGift, + faGlobe, faHand, faHouseChimney, faLocationDot, @@ -92,6 +99,7 @@ library.add( faPersonCircleQuestion, faPlus, faQrcode, + faQuestion, faRotate, faShareNodes, faSpinner, diff --git a/src/router/index.ts b/src/router/index.ts index 49b3376..af3e32c 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -91,6 +91,14 @@ const routes: Array = [ component: () => import(/* webpackChunkName: "help" */ "../views/HelpView.vue"), }, + { + path: "/help-notifications", + name: "help-notifications", + component: () => + import( + /* webpackChunkName: "help-notifications" */ "../views/HelpNotificationsView.vue" + ), + }, { path: "/identity-switcher", name: "identity-switcher", diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index e33d0d2..cdd8d1e 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -288,6 +288,14 @@ +
+ +
+
-
- -
-

Claim Server

+ + + +
+ +
+ +
+

+ +

+
+ + +

+ Notification Help +

+
+ +
+

Here are things to try to get notifications working.

+ +

Test

+

Somehow call the service-worker self.showNotification

+ +

Check OS-level permissions

+

+ Walk-throughs & screenshots, maybe for all combinations of OS & + browsers. +

+ +

Check browser-level permissions

+

Walk-throughs & screenshots for browser settings

+ +

Explain full reset to start again

+

+ Walk-throughs for clearing everything & subscribing anew to get a + message +

+ +

Auto-detection

+

Show results of auto-detection whether they're turned on

+
+
+ + diff --git a/src/views/HelpView.vue b/src/views/HelpView.vue index 37cae27..f16d1f5 100644 --- a/src/views/HelpView.vue +++ b/src/views/HelpView.vue @@ -181,6 +181,21 @@ different page.

+

+ How do I access even more functionality? +

+

+ There is an "Advanced" section at the bottom of the Account + page. +

+

+ There is a even more functionality in a mobile app (and more + documentation) at + + EndorserSearch.com + +

+

What is your privacy policy?

See diff --git a/src/views/NewEditProjectView.vue b/src/views/NewEditProjectView.vue index 62d5c71..eaff9b0 100644 --- a/src/views/NewEditProjectView.vue +++ b/src/views/NewEditProjectView.vue @@ -26,20 +26,26 @@ type="text" placeholder="Idea Name" class="block w-full rounded border border-slate-400 mb-4 px-3 py-2" - v-model="projectName" + v-model="fullClaim.name" />

- {{ description.length }}/5000 max. characters + {{ fullClaim.description.length }}/5000 max. characters
+ +
@@ -136,13 +142,17 @@ export default class NewEditProjectView extends Vue { activeDid = ""; apiServer = ""; - description = ""; errorMessage = ""; + fullClaim: PlanVerifiableCredential = { + "@context": "https://schema.org", + "@type": "PlanAction", + name: "", + description: "", + }; // this default is only to avoid errors before plan is loaded includeLocation = false; latitude = 0; longitude = 0; numAccounts = 0; - projectName = ""; zoom = 2; async beforeCreate() { @@ -214,9 +224,12 @@ export default class NewEditProjectView extends Vue { try { const resp = await this.axios.get(url, { headers }); if (resp.status === 200) { - const claim = resp.data.claim; - this.projectName = claim.name; - this.description = claim.description; + this.fullClaim = resp.data.claim; + if (this.fullClaim?.location) { + this.includeLocation = true; + this.latitude = this.fullClaim.location.geo.latitude; + this.longitude = this.fullClaim.location.geo.longitude; + } } } catch (error) { console.error("Got error retrieving that project", error); @@ -225,13 +238,7 @@ export default class NewEditProjectView extends Vue { private async SaveProject(identity: IIdentifier) { // Make a claim - const vcClaim: PlanVerifiableCredential = { - "@context": "https://schema.org", - "@type": "PlanAction", - name: this.projectName, - description: this.description, - identifier: this.projectId || undefined, - }; + const vcClaim: PlanVerifiableCredential = this.fullClaim; if (this.projectId) { vcClaim.identifier = this.projectId; } @@ -314,8 +321,8 @@ export default class NewEditProjectView extends Vue { error?: { message?: string }; }>; if (serverError) { + console.log("Got error from server", serverError); if (Object.prototype.hasOwnProperty.call(serverError, "message")) { - console.log(serverError); userMessage = serverError.response?.data?.error?.message || ""; // This is info for the user. this.$notify( { diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index b58f754..a72ff9d 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -35,7 +35,7 @@ {{ issuer }}
-
+
{{ timeSince }}
@@ -45,8 +45,13 @@ :href="getOpenStreetMapUrl()" target="_blank" class="underline" - > - Map View + >Map View + +
+
+ + {{ domainForWebsite(this.url) }}
@@ -56,8 +61,11 @@
{{ truncatedDesc }} - Read More... Read More
@@ -65,7 +73,7 @@ Read Less- Read Less
@@ -167,8 +175,10 @@ {{ didInfo(offer.agentDid, activeDid, allMyDids, allContacts) }} - - {{ offer.amount }} + {{ offer.amount }}
@@ -195,9 +205,11 @@ > {{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }} - - {{ give.amount }} + + {{ give.amount }}
@@ -265,6 +277,7 @@ import { accountsDB, db } from "@/db/index"; import { Contact } from "@/db/tables/contacts"; import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { accessToken } from "@/libs/crypto"; +import { isGlobalUri } from "@/libs/util"; import { didInfo, GiverInputInfo, @@ -307,6 +320,7 @@ export default class ProjectViewView extends Vue { timeSince = ""; truncatedDesc = ""; truncateLength = 40; + url = ""; async created() { await db.open(); @@ -408,6 +422,7 @@ export default class ProjectViewView extends Vue { this.truncatedDesc = this.description.slice(0, this.truncateLength); this.latitude = resp.data.claim?.location?.geo?.latitude || 0; this.longitude = resp.data.claim?.location?.geo?.longitude || 0; + this.url = resp.data.claim?.url || ""; } else if (resp.status === 404) { // actually, axios throws an error so we never get here this.$notify( @@ -625,5 +640,52 @@ export default class ProjectViewView extends Vue { openOfferDialog() { (this.$refs.customOfferDialog as OfferDialog).open(); } + + UNIT_CODES: Record> = { + BTC: { + name: "Bitcoin", + faIcon: "bitcoin-sign", + }, + HUR: { + name: "hours", + faIcon: "clock", + }, + USD: { + name: "US Dollars", + faIcon: "dollar", + }, + }; + + iconForUnitCode(unitCode: string) { + return this.UNIT_CODES[unitCode]?.faIcon || "question"; + } + + // return an HTTPS URL if it's not a global URL + addScheme(url: string) { + if (!isGlobalUri(url)) { + return "https://" + url; + } + return url; + } + + // return just the domain for display, if possible + domainForWebsite(url: string) { + try { + const hostname = new URL(url).hostname; + if (!hostname) { + // happens for non-http URLs + return url; + } else if (url.endsWith(hostname)) { + // it's just the domain + return hostname; + } else { + // there's more, but don't bother displaying the whole thing + return hostname + "..."; + } + } catch (error: unknown) { + // must not be a valid URL + return url; + } + } }