Compare commits

...

15 Commits

Author SHA1 Message Date
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
11 changed files with 199 additions and 133 deletions

View File

@@ -6,23 +6,25 @@ tasks:
- 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 "mute notifications"
- remove sleep in py-push-server app.py?
- see if we can detect OS-level notifications if turned off
- write troubleshooting docs for notifications
- make the "App Notifications" toggle on when they turn notifications on
- make the "App Notifications" toggle off when they turn notifications off
- in py-push-server, when sending a push to a subscriber and we get on a 410 "error #106", delete the subscription record
- https://gitea.anomalistdesign.com/trent_larson/py-push-server/pulls/3/files
- remove "notification push server" advanced setting since it only makes sense on the current domain
- prompt user to install on their home screen
- .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")
- .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
- .5 Add infinite scroll to gifts on the home page
- .5 If notifications are not enabled, add message to front page with link/button to enable
- fix notification error when first loading the app
- 01 server - show all claim details when issued by the issuer
- add note after contact addition that they can see your info
- enhance help page instructions for debugging
- add way to test quickly a push notification
@@ -48,8 +50,10 @@ tasks:
- 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
- remove 'rowid' references (that are sqlite-specific)
- 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
- 01 send visibility signal as a VC and store it
- .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?
@@ -124,6 +128,8 @@ tasks:
- 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.
- 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:
- 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

View File

@@ -328,16 +328,23 @@ export default class App extends Vue {
);
}
} catch (error) {
console.error("Got an error initializing notifications:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Setting Notifications",
text: "Got an error setting notifications.",
},
-1,
);
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);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Setting Notifications",
text: "Got an error setting notifications.",
},
-1,
);
}
}
}

View File

@@ -32,8 +32,24 @@
</span>
</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 -->
<!-- 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
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"
@@ -127,44 +143,13 @@
></div>
</div>
</label>
<label
for="toggleMuteNotifications"
class="flex items-center justify-between cursor-pointer mt-4"
@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>
</div>
<h3 class="text-sm uppercase font-semibold mb-3">Data</h3>
<router-link
:to="{ name: 'seed-backup' }"
v-if="activeDid"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
Backup Identifier Seed
@@ -328,7 +313,7 @@
:to="{ name: 'identity-switcher' }"
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>
<button

View File

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

View File

@@ -171,7 +171,7 @@
<button
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-l-md"
@click="onClickAddGive(activeDid, contact.did)"
title="givenByMeDescriptions[contact.did]"
:title="givenByMeDescriptions[contact.did] || ''"
>
To:
{{
@@ -190,7 +190,7 @@
<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"
@click="onClickAddGive(contact.did, activeDid)"
title="givenToMeDescriptions[contact.did]"
:title="givenToMeDescriptions[contact.did] || ''"
>
From:
{{
@@ -367,6 +367,10 @@ export default class ContactsView extends Vue {
}
async loadGives() {
if (!this.activeDid) {
return;
}
const handleResponse = (
resp: { status: number; data: { data: GiveServerRecord[] } },
descriptions: Record<string, string>,
@@ -401,11 +405,11 @@ export default class ContactsView extends Vue {
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Retrieval Error",
text:
"Got an error retrieving your " +
(useRecipient ? "given" : "received") +
" time from the server.",
" data from the server.",
},
-1,
);
@@ -456,12 +460,13 @@ export default class ContactsView extends Vue {
this.givenToMeConfirmed = givenToMeConfirmed;
this.givenToMeUnconfirmed = givenToMeUnconfirmed;
} catch (error) {
console.log("Error loading gives", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Server Error",
text: error as string,
title: "Load Error",
text: "Got an error loading your gives.",
},
-1,
);
@@ -705,6 +710,7 @@ export default class ContactsView extends Vue {
);
}
} catch (error) {
console.error("Error when registering:", error);
let userMessage = "There was an error. See logs for more info.";
const serverError = error as AxiosError;
if (serverError) {
@@ -721,7 +727,7 @@ export default class ContactsView extends Vue {
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Registration Error",
text: userMessage,
},
-1,
@@ -767,27 +773,28 @@ export default class ContactsView extends Vue {
} else {
console.error(
"Got some bad server response when setting visibility: ",
resp.status,
resp,
);
const message =
resp.data.error?.message || "Bad server response of " + resp.status;
resp.data.error?.message || "Got some error setting visibility.";
this.$notify(
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Error Setting Visibility",
text: message,
},
-1,
);
}
} catch (err) {
console.error("Got some server error when setting visibility:", err);
console.error("Got some error when setting visibility:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Error Setting Visibility",
text: "Check connectivity and try again.",
},
-1,
@@ -830,19 +837,19 @@ export default class ContactsView extends Vue {
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Error Checking Visibility",
text: message,
},
-1,
);
}
} 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(
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Error Checking Visibility",
text: "Check connectivity and try again.",
},
-1,
@@ -1028,6 +1035,7 @@ export default class ContactsView extends Vue {
}
}
} catch (error) {
console.log("Error in createAndSubmitGive: ", error);
let userMessage = "There was an error. See logs for more info.";
const serverError = error as AxiosError;
if (serverError) {
@@ -1044,7 +1052,7 @@ export default class ContactsView extends Vue {
{
group: "alert",
type: "danger",
title: "Server Error",
title: "Error Sending Give",
text: userMessage,
},
-1,

View File

@@ -28,13 +28,35 @@
<p>Somehow call the service-worker self.showNotification</p>
<h2 class="text-xl font-semibold">Check OS-level permissions</h2>
<p>
<div>
Walk-throughs & screenshots, maybe for all combinations of OS &
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>
<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>
<p>

View File

@@ -13,11 +13,11 @@
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
>
<p class="text-lg mb-3">
You need an <b>identifier</b> before you can record others' giving.
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-slate-500 text-white px-2 py-3 rounded-md"
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
>
@@ -27,17 +27,17 @@
v-else-if="!isRegistered"
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
>
Someone must register your account before you can record others' giving.
Someone must register your account before you can record anyone's gives.
To do this:
<router-link
:to="{ name: 'contact-qr' }"
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
class="block text-center text-md font-bold uppercase bg-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-slate-500 text-white px-2 py-3 rounded-md"
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
>
@@ -103,42 +103,52 @@
showGivenToUser="true"
/>
<!-- Results List -->
<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>
<InfiniteScroll @reached-bottom="loadMoreGives">
<ul class="border-t border-slate-300">
<li
class="border-b border-slate-300 py-2"
v-for="record in feedData"
:key="record.jwtId"
>
<div
class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm"
v-if="record.jwtId == feedLastViewedClaimId"
>
You've seen all the following
</div>
<div class="flex">
<fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa>
<span class="">{{ this.giveDescription(record) }}</span>
<a @click="onClickLoadClaim(record.jwtId)">
<fa icon="circle-info" class="pl-2 pt-1 text-slate-500"></fa>
</a>
</div>
</li>
</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>
<ul class="border-t border-slate-300">
<li
class="border-b border-slate-300 py-2"
v-for="record in feedData"
:key="record.jwtId"
>
<div
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"
>
You've seen all the following
</div>
<div class="flex">
<fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa>
<span class="">{{ this.giveDescription(record) }}</span>
<a @click="onClickLoadClaim(record.jwtId)">
<fa icon="circle-info" class="pl-2 pt-1 text-slate-500"></fa>
</a>
</div>
</li>
</ul>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import EntityIcon from "@/components/EntityIcon.vue";
import GiftedDialog from "@/components/GiftedDialog.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue";
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 { accessToken } from "@/libs/crypto";
import {
@@ -146,11 +156,7 @@ import {
GiverInputInfo,
GiveServerRecord,
} 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 { Account } from "@/db/tables/accounts";
interface Notification {
group: string;
@@ -160,7 +166,7 @@ interface Notification {
}
@Component({
components: { GiftedDialog, QuickNav, EntityIcon },
components: { GiftedDialog, QuickNav, EntityIcon, InfiniteScroll },
})
export default class HomeView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
@@ -169,10 +175,9 @@ export default class HomeView extends Vue {
allContacts: Array<Contact> = [];
allMyDids: Array<string> = [];
apiServer = "";
feedAllLoaded = false;
feedData = [];
feedPreviousOldestId?: string;
feedLastViewedId?: string;
feedLastViewedClaimId?: string;
isHiddenSpinner = true;
isRegistered = false;
numAccounts = 0;
@@ -212,9 +217,12 @@ export default class HomeView extends Vue {
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
this.allContacts = await db.contacts.toArray();
this.feedLastViewedId = settings?.lastViewedClaimId;
this.feedLastViewedClaimId = settings?.lastViewedClaimId;
this.isRegistered = !!settings?.isRegistered;
// this returns a Promise but we don't need to wait for it
this.updateAllFeed();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
this.$notify(
@@ -257,24 +265,33 @@ export default class HomeView extends Vue {
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() {
this.isHiddenSpinner = false;
await this.retrieveClaims(this.apiServer, this.feedPreviousOldestId)
await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
.then(async (results) => {
if (results.data.length > 0) {
this.feedData = this.feedData.concat(results.data);
this.feedAllLoaded = results.hitLimit;
this.feedPreviousOldestId =
results.data[results.data.length - 1].jwtId;
// The following update is only done on the first load.
if (
this.feedLastViewedId == null ||
this.feedLastViewedId < results.data[0].jwtId
this.feedLastViewedClaimId == null ||
this.feedLastViewedClaimId < results.data[0].jwtId
) {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
lastViewedClaimId: results.data[0].jwtId,
});
// but not for this page because we need to remember what it was before
}
}
})
@@ -284,17 +301,22 @@ export default class HomeView extends Vue {
{
group: "alert",
type: "danger",
title: "Export Error",
title: "Feed Error",
text: e.userMessage || "There was an error retrieving feed data.",
},
-1,
);
});
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 response = await fetch(
endorserApiServer + "/api/v2/report/gives?" + beforeQuery,

View File

@@ -37,7 +37,7 @@
maxlength="5000"
></textarea>
<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>
<input

View File

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

View File

@@ -88,8 +88,8 @@
</button>
</div>
<div class="mb-4">
<div v-if="activeDid" class="text-center">
<div v-if="activeDid" class="mb-4">
<div class="text-center">
<button
@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"
@@ -99,17 +99,16 @@
</div>
</div>
<div>
<div v-if="activeDid" class="text-center">
<div v-if="activeDid">
<div class="text-center">
<button
@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"
>
I gave&hellip;
</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>
<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">
<li @click="openGiftDialog()">
@@ -234,28 +233,32 @@
<h3 class="text-sm uppercase font-semibold mb-3">
Contributions To This Idea
</h3>
<ul>
<li v-for="plan in fulfillersToThis" :key="plan.handleId">
<!-- centering because long, wrapped project names didn't left align with blank or "text-left" -->
<div class="text-center">
<div v-for="plan in fulfillersToThis" :key="plan.handleId">
<button
@click="onClickLoadProject(plan.handleId)"
class="text-blue-500"
>
{{ plan.name }}
</button>
</li>
</ul>
</div>
</div>
</div>
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
<h3 class="text-sm uppercase font-semibold mb-3">
Contributions By This Idea
</h3>
<button
@click="onClickLoadProject(fulfilledByThis.handleId)"
class="text-blue-500"
>
{{ fulfilledByThis.name }}
</button>
<!-- centering because long, wrapped project names didn't left align with blank or "text-left" -->
<div class="text-center">
<button
@click="onClickLoadProject(fulfilledByThis.handleId)"
class="text-blue-500"
>
{{ fulfilledByThis.name }}
</button>
</div>
</div>
</div>
</div>
@@ -356,12 +359,6 @@ export default class ProjectViewView extends Vue {
.equals(activeDid)
.first()) as Account;
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"Attempted to load project records with no identity available.",
);
}
return identity;
}

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.)
* 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.)
# 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