other-smalls #89

Merged
trentlarson merged 4 commits from other-smalls into master 10 months ago
  1. 4
      README.md
  2. 43
      project.task.yaml
  3. 6
      src/libs/endorserServer.ts
  4. 3
      src/libs/util.ts
  5. 8
      src/main.ts
  6. 16
      src/views/AccountViewView.vue
  7. 1
      src/views/ContactsView.vue
  8. 15
      src/views/HelpView.vue
  9. 41
      src/views/NewEditProjectView.vue
  10. 84
      src/views/ProjectViewView.vue

4
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.

43
project.task.yaml

@ -11,14 +11,32 @@ tasks:
- .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 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
- 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 +44,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 +59,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

6
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";

3
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+.-]+:/));
};

8
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,

16
src/views/AccountViewView.vue

@ -288,6 +288,14 @@
</div>
</label>
<div class="flex py-2">
<button class="text-blue-500">
<router-link :to="{ name: 'statistics' }" class="block text-center">
See Global Animated History of Giving
</router-link>
</button>
</div>
<div class="flex py-2">
<button class="text-blue-500">
<!-- id used by puppeteer test script -->
@ -301,14 +309,6 @@
</button>
</div>
<div class="flex py-2">
<button class="text-blue-500">
<router-link :to="{ name: 'statistics' }" class="block text-center">
See Achievements & Statistics
</router-link>
</button>
</div>
<div class="flex py-4">
<h2 class="text-slate-500 text-sm font-bold mb-2">Claim Server</h2>
<input

1
src/views/ContactsView.vue

@ -957,6 +957,7 @@ export default class ContactsView extends Vue {
}
}
// similar function is in endorserServer.ts
private async createAndSubmitGive(
identity: IIdentifier,
fromDid: string,

15
src/views/HelpView.vue

@ -181,6 +181,21 @@
different page.
</p>
<h2 class="text-xl font-semibold">
How do I access even more functionality?
</h2>
<p>
There is an "Advanced" section at the bottom of the Account
<fa icon="circle-user" /> page.
</p>
<p>
There is a even more functionality in a mobile app (and more
documentation) at
<a href="https://endorser.ch" class="text-blue-500">
EndorserSearch.com
</a>
</p>
<h2 class="text-xl font-semibold">What is your privacy policy?</h2>
<p>
See

41
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"
/>
<textarea
placeholder="Description"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
rows="5"
v-model="description"
v-model="fullClaim.description"
maxlength="5000"
></textarea>
<div class="text-xs text-slate-500 italic -mt-3 mb-4">
{{ description.length }}/5000 max. characters
{{ fullClaim.description.length }}/5000 max. characters
</div>
<input
v-model="fullClaim.url"
placeholder="Website"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
/>
<div class="flex items-center mb-4">
<input
type="checkbox"
@ -72,7 +78,7 @@
name="OpenStreetMap"
/>
<l-marker
v-if="latitude || longitude"
v-if="latitude && longitude"
:lat-lng="[latitude, longitude]"
@click="maybeEraseLatLong()"
/>
@ -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(
{

84
src/views/ProjectViewView.vue

@ -35,7 +35,7 @@
<fa icon="user" class="fa-fw text-slate-400"></fa>
{{ issuer }}
</div>
<div>
<div v-if="timeSince">
<fa icon="calendar" class="fa-fw text-slate-400"></fa>
{{ timeSince }}
</div>
@ -45,8 +45,13 @@
:href="getOpenStreetMapUrl()"
target="_blank"
class="underline"
>
Map View
>Map View
</a>
</div>
<div v-if="url">
<fa icon="globe" class="fa-fw text-slate-400"></fa>
<a :href="addScheme(url)" target="_blank" class="underline"
>{{ domainForWebsite(this.url) }}
</a>
</div>
</div>
@ -56,8 +61,11 @@
<div class="text-sm text-slate-500">
<div v-if="!expanded">
{{ truncatedDesc }}
<a v-if="description.length >= truncateLength" @click="expandText"
>Read More</a
<a
v-if="description.length >= truncateLength"
@click="expandText"
class="uppercase text-xs font-semibold text-slate-700"
>... Read More</a
>
</div>
<div v-else>
@ -65,7 +73,7 @@
<a
@click="collapseText"
class="uppercase text-xs font-semibold text-slate-700"
>Read Less</a
>- Read Less</a
>
</div>
</div>
@ -167,8 +175,10 @@
{{ didInfo(offer.agentDid, activeDid, allMyDids, allContacts) }}
</span>
<span v-if="offer.amount">
<fa icon="coins" class="fa-fw text-slate-400"></fa>
{{ offer.amount }}
<fa
:icon="iconForUnitCode(offer.unit)"
class="fa-fw text-slate-400"
/>{{ offer.amount }}
</span>
</div>
<div v-if="offer.objectDescription" class="text-slate-500">
@ -195,9 +205,11 @@
><fa icon="user" class="fa-fw text-slate-400"></fa>
{{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }}
</span>
<span v-if="give.amount"
><fa icon="coins" class="fa-fw text-slate-400"></fa>
{{ give.amount }}
<span v-if="give.amount">
<fa
:icon="iconForUnitCode(give.unit)"
class="fa-fw text-slate-400"
/>{{ give.amount }}
</span>
</div>
<div v-if="give.description" class="text-slate-500">
@ -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<string, Record<string, string>> = {
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;
}
}
}
</script>

Loading…
Cancel
Save