Compare commits

...

14 Commits

Author SHA1 Message Date
7f0d10d93d Merge branch 'master' into sw-cleanup 2023-12-08 23:08:03 -05:00
Matthew Raymer
a60beb483c Adding alert dialogs 2023-12-08 23:05:24 -05:00
6045975b79 add more tasks for notifications work 2023-12-07 20:44:48 -07:00
a6bb036ceb fix name of new HelpNotificationsView class 2023-12-07 20:35:29 -07:00
1e2ad85547 add dedicated help page for looking into notifications 2023-12-07 20:33:17 -07:00
Matthew Raymer
3e2723b744 Added auto-control on notification toggle -- be sure to empty browser cache in Storage to assure latest scripts are executing 2023-12-07 04:20:41 -05:00
4daffe8f40 doc: fix note about remaining py_push_server work 2023-12-06 14:35:57 -07:00
efb1922826 Merge pull request 'other-smalls' (#89) from other-smalls into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#89
2023-12-06 16:22:55 -05:00
c6e10bfdad update tasks 2023-12-05 20:03:19 -07:00
bb122be319 add URL for plans 2023-12-05 19:55:44 -07:00
3f436476a2 fix project loading & saving to include all the claim data 2023-12-05 18:47:56 -07:00
a77d20b572 show appropriate icon next to amount numbers (and some docs) 2023-12-05 17:58:46 -07:00
69a25ddd6c Merge pull request 'don't show non-message to user; fix API server setting; misc doc & task stuff' (#88) from adjust-note into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#88
2023-12-05 03:43:27 -05:00
Matthew Raymer
9846cf3e4c Some linting and further documenting 2023-12-02 22:08:04 -05:00
13 changed files with 285 additions and 69 deletions

View File

@@ -26,6 +26,11 @@ If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js,
npm run build
```
```
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.
@@ -59,6 +64,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.

View File

@@ -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

View File

@@ -426,16 +426,13 @@ export default class App extends Vue {
this.subscribeToPush()
.then(() => {
console.log("Subscribed successfully.");
// Assuming the subscription object is available
return navigator.serviceWorker.ready;
})
.then((registration) => {
// Fetch the existing subscription object from the registration
return registration.pushManager.getSubscription();
})
.then((subscription) => {
if (subscription) {
console.log(subscription);
return this.sendSubscriptionToServer(subscription);
} else {
throw new Error("Subscription object is not available.");
@@ -449,15 +446,16 @@ export default class App extends Vue {
"Subscription or server communication failed:",
error,
);
alert( "Subscription or server communication failed. Try again in a while." );
});
})
.catch((error) => {
console.error("An error occurred:", error);
// Handle error appropriately here
alert( "Some error occurred." + error );
});
}
// Function to convert URL base64 to Uint8Array
private urlBase64ToUint8Array(base64String: string): Uint8Array {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)

View File

@@ -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";
@@ -157,8 +163,8 @@ export function didInfo(
return contact
? contact.name || "Contact With No Name"
: isHiddenDid(did)
? "Someone Not In Network"
: "Someone Not In Contacts";
? "Someone Not In Network"
: "Someone Not In Contacts";
}
export interface ResultWithType {

3
src/libs/util.ts Normal file
View File

@@ -0,0 +1,3 @@
export const isGlobalUri = (uri: string) => {
return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/));
};

View File

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

View File

@@ -91,6 +91,14 @@ const routes: Array<RouteRecordRaw> = [
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",

View File

@@ -105,7 +105,12 @@
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input type="checkbox" name="toggleNotifications" class="sr-only" />
<input
type="checkbox"
v-model="toggleNotifications"
name="toggleNotifications"
class="sr-only"
/>
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
@@ -136,6 +141,7 @@
type="checkbox"
name="toggleMuteNotifications"
class="sr-only"
disabled
/>
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
@@ -288,6 +294,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 +315,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
@@ -447,6 +453,16 @@ export default class AccountViewView extends Vue {
showAdvanced = false;
private isSubscribed = false;
get toggleNotifications() {
return this.isSubscribed;
}
set toggleNotifications(value) {
this.isSubscribed = value;
}
public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
try {
// Open the accounts database
@@ -529,6 +545,7 @@ export default class AccountViewView extends Vue {
* @throws Will display specific messages to the user based on different errors.
*/
async created() {
console.error("created");
try {
await db.open();
@@ -547,6 +564,18 @@ export default class AccountViewView extends Vue {
}
}
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.
@@ -756,7 +785,7 @@ export default class AccountViewView extends Vue {
});
this.isRegistered = true;
} catch (err) {
console.log("Got an error updating settings:", err);
console.error("Got an error updating settings:", err);
this.$notify(
{
group: "alert",
@@ -798,10 +827,9 @@ export default class AccountViewView extends Vue {
const data = error.response?.data as ErrorResponse;
this.limitsMessage =
(data?.error?.message as string) || "Bad server response.";
console.log(
console.error(
"Got bad response retrieving limits, which usually means user isn't registered. Server says:",
this.limitsMessage,
//error,
);
} else if (
error instanceof Error &&

View File

@@ -67,8 +67,8 @@
showGiveTotals
? "Total"
: showGiveConfirmed
? "Confirmed"
: "Unconfirmed"
? "Confirmed"
: "Unconfirmed"
}}
</button>
<br />
@@ -957,6 +957,7 @@ export default class ContactsView extends Vue {
}
}
// similar function is in endorserServer.ts
private async createAndSubmitGive(
identity: IIdentifier,
fromDid: string,

View File

@@ -0,0 +1,65 @@
<template>
<QuickNav />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<!-- Breadcrumb -->
<div class="mb-8">
<!-- Back -->
<div class="text-lg text-center font-light relative px-7">
<h1
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
@click="$router.back()"
>
<fa icon="chevron-left" class="fa-fw"></fa>
</h1>
</div>
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Notification Help
</h1>
</div>
<div>
<p>Here are things to try to get notifications working.</p>
<h2 class="text-xl font-semibold">Test</h2>
<p>Somehow call the service-worker self.showNotification</p>
<h2 class="text-xl font-semibold">Check OS-level permissions</h2>
<p>
Walk-throughs & screenshots, maybe for all combinations of OS &
browsers.
</p>
<h2 class="text-xl font-semibold">Check browser-level permissions</h2>
<p>Walk-throughs & screenshots for browser settings</p>
<h2 class="text-xl font-semibold">Explain full reset to start again</h2>
<p>
Walk-throughs for clearing everything & subscribing anew to get a
message
</p>
<h2 class="text-xl font-semibold">Auto-detection</h2>
<p>Show results of auto-detection whether they're turned on</p>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import QuickNav from "@/components/QuickNav.vue";
interface Notification {
group: string;
type: string;
title: string;
text: string;
}
@Component({ components: { QuickNav } })
export default class HelpNotificationsView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
}
</script>

View File

@@ -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

View File

@@ -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(
{

View File

@@ -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>