Browse Source

add large notice when user has a new offer to them

master
Trent Larson 3 weeks ago
parent
commit
3c790dceb7
  1. 6
      src/components/OfferDialog.vue
  2. 7
      src/db/tables/settings.ts
  3. 11
      src/libs/endorserServer.ts
  4. 5
      src/router/index.ts
  5. 2
      src/views/ContactGiftingView.vue
  6. 38
      src/views/ContactsView.vue
  7. 50
      src/views/HomeView.vue
  8. 111
      src/views/NewActivityView.vue

6
src/components/OfferDialog.vue

@ -207,9 +207,9 @@ export default class OfferDialog extends Vue {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Error", title: "Error",
text: "You must select an identifier before you can record an offer.", text: "You must select an identity before you can record an offer.",
}, },
-1, 7000,
); );
return; return;
} }
@ -264,7 +264,7 @@ export default class OfferDialog extends Vue {
title: "Success", title: "Success",
text: "That offer was recorded.", text: "That offer was recorded.",
}, },
10000, 5000,
); );
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

7
src/db/tables/settings.ts

@ -31,8 +31,13 @@ export type Settings = {
isRegistered?: boolean; isRegistered?: boolean;
imageServer?: string; imageServer?: string;
lastName?: string; // deprecated - put all names in firstName lastName?: string; // deprecated - put all names in firstName
lastAckedOfferToUserJwtId?: string; // the last JWT ID for offer-to-user that they've acknowledged
// The claim list has a most recent one used in notifications that's separate from the last viewed
lastNotifiedClaimId?: string; lastNotifiedClaimId?: string;
lastViewedClaimId?: string; lastViewedClaimId?: string;
passkeyExpirationMinutes?: number; // passkey access token time-to-live in minutes passkeyExpirationMinutes?: number; // passkey access token time-to-live in minutes
profileImageUrl?: string; // may be null if unwanted for a particular account profileImageUrl?: string; // may be null if unwanted for a particular account
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
@ -53,7 +58,7 @@ export type Settings = {
webPushServer?: string; // Web Push server URL webPushServer?: string; // Web Push server URL
}; };
export function isAnyFeedFilterOn(settings: Settings): boolean { export function checkIsAnyFeedFilterOn(settings: Settings): boolean {
return !!(settings?.filterFeedByNearby || settings?.filterFeedByVisible); return !!(settings?.filterFeedByNearby || settings?.filterFeedByVisible);
} }

11
src/libs/endorserServer.ts

@ -587,6 +587,17 @@ export async function setPlanInCache(
planCache.set(handleId, planSummary); planCache.set(handleId, planSummary);
} }
export async function getNewOffersToUser(axios: Axios, apiServer: string, activeDid: string, lastAckedOfferToUserJwtId?: string) {
let url = `${apiServer}/api/v2/report/offers?recipientDid=${activeDid}`;
if (lastAckedOfferToUserJwtId) {
url += "&afterId=" + lastAckedOfferToUserJwtId;
}
const headers = await getHeaders(activeDid);
const response = await axios.get(url, { headers });
const offers = response.data.data;
return offers;
}
/** /**
* Construct GiveAction VC for submission to server * Construct GiveAction VC for submission to server
* *

5
src/router/index.ts

@ -133,6 +133,11 @@ const routes: Array<RouteRecordRaw> = [
name: "invite-one", name: "invite-one",
component: () => import("../views/InviteOneView.vue"), component: () => import("../views/InviteOneView.vue"),
}, },
{
path: "/new-activity",
name: "new-activity",
component: () => import("../views/NewActivityView.vue"),
},
{ {
path: "/new-edit-account", path: "/new-edit-account",
name: "new-edit-account", name: "new-edit-account",

2
src/views/ContactGiftingView.vue

@ -123,7 +123,7 @@ export default class ContactGiftingView extends Vue {
err.message || err.message ||
"There was an error retrieving your settings or contacts.", "There was an error retrieving your settings or contacts.",
}, },
-1, 5000,
); );
} }
} }

38
src/views/ContactsView.vue

@ -88,7 +88,7 @@
@click="toggleShowContactAmounts()" @click="toggleShowContactAmounts()"
> >
{{ {{
showGiveNumbers ? "Hide Given Hours etc" : "Show Given Hours etc" showGiveNumbers ? "Hide Given Hours, etc" : "Show Given Hours, etc"
}} }}
</button> </button>
</div> </div>
@ -182,44 +182,40 @@
> >
<button <button
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-l-md" class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-l-md"
@click="confirmShowGiftedDialog(activeDid, contact.did)" @click="confirmShowGiftedDialog(contact.did, activeDid)"
:title="givenByMeDescriptions[contact.did] || ''" :title="givenToMeDescriptions[contact.did] || ''"
> >
To: From:
<br /> <br />
{{ {{
/* eslint-disable prettier/prettier */ /* eslint-disable prettier/prettier */
this.showGiveTotals this.showGiveTotals
? ((givenByMeConfirmed[contact.did] || 0) ? ((givenToMeConfirmed[contact.did] || 0)
+ (givenByMeUnconfirmed[contact.did] || 0)) + (givenToMeUnconfirmed[contact.did] || 0))
: this.showGiveConfirmed : this.showGiveConfirmed
? (givenByMeConfirmed[contact.did] || 0) ? (givenToMeConfirmed[contact.did] || 0)
: (givenByMeUnconfirmed[contact.did] || 0) : (givenToMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */ /* eslint-enable prettier/prettier */
}} }}
<br />
<fa icon="plus" />
</button> </button>
<button <button
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l" class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l"
@click="confirmShowGiftedDialog(contact.did, this.activeDid)" @click="confirmShowGiftedDialog(activeDid, contact.did)"
:title="givenToMeDescriptions[contact.did] || ''" :title="givenByMeDescriptions[contact.did] || ''"
> >
From: To:
<br /> <br />
{{ {{
/* eslint-disable prettier/prettier */ /* eslint-disable prettier/prettier */
this.showGiveTotals this.showGiveTotals
? ((givenToMeConfirmed[contact.did] || 0) ? ((givenByMeConfirmed[contact.did] || 0)
+ (givenToMeUnconfirmed[contact.did] || 0)) + (givenByMeUnconfirmed[contact.did] || 0))
: this.showGiveConfirmed : this.showGiveConfirmed
? (givenToMeConfirmed[contact.did] || 0) ? (givenByMeConfirmed[contact.did] || 0)
: (givenToMeUnconfirmed[contact.did] || 0) : (givenByMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */ /* eslint-enable prettier/prettier */
}} }}
<br />
<fa icon="plus" />
</button> </button>
<button <button
@ -587,7 +583,7 @@ export default class ContactsView extends Vue {
(useRecipient ? "given" : "received") + (useRecipient ? "given" : "received") +
" data from the server.", " data from the server.",
}, },
-1, 5000,
); );
} }
}; };
@ -1167,7 +1163,7 @@ export default class ContactsView extends Vue {
title: "Error Updating Contact Setting", title: "Error Updating Contact Setting",
text: "The setting may not have saved. Try again, maybe after restarting the app.", text: "The setting may not have saved. Try again, maybe after restarting the app.",
}, },
-1, 5000,
); );
console.error( console.error(
"Telling user to try again after contact-amounts setting update because:", "Telling user to try again after contact-amounts setting update because:",

50
src/views/HomeView.vue

@ -196,6 +196,32 @@
</button> </button>
</h2> </h2>
</div> </div>
<div
v-if="numNewOffersToUser"
@click="goToActivityToUserPage"
class="border-t p-2 border-slate-300"
>
<div class="flex justify-center">
<div
v-if="numNewOffersToUser"
class="bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-4 py-4 rounded-md text-white"
>
<span class="block text-center text-6xl">
{{ numNewOffersToUser }}
</span>
<p>
new offer{{ numNewOffersToUser === 1 ? "" : "s" }} to you
</p>
</div>
</div>
<div class="flex justify-end mt-2">
<button class="text-blue-500">
View All New Activity For You
</button>
</div>
</div>
<InfiniteScroll @reached-bottom="loadMoreGives"> <InfiniteScroll @reached-bottom="loadMoreGives">
<ul id="listLatestActivity" class="border-t border-slate-300"> <ul id="listLatestActivity" class="border-t border-slate-300">
<li <li
@ -204,7 +230,7 @@
:key="record.jwtId" :key="record.jwtId"
> >
<div <div
class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold text-sm" class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
v-if="record.jwtId == feedLastViewedClaimId" v-if="record.jwtId == feedLastViewedClaimId"
> >
You've already seen all the following You've already seen all the following
@ -345,7 +371,7 @@ import {
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { import {
BoundingBox, BoundingBox,
isAnyFeedFilterOn, checkIsAnyFeedFilterOn,
MASTER_SETTINGS_KEY, MASTER_SETTINGS_KEY,
} from "@/db/tables/settings"; } from "@/db/tables/settings";
import { import {
@ -354,6 +380,7 @@ import {
didInfoForContact, didInfoForContact,
fetchEndorserRateLimits, fetchEndorserRateLimits,
getHeaders, getHeaders,
getNewOffersToUser,
getPlanFromCache, getPlanFromCache,
GiveSummaryRecord, GiveSummaryRecord,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
@ -418,6 +445,8 @@ export default class HomeView extends Vue {
isFeedFilteredByNearby = false; isFeedFilteredByNearby = false;
isFeedLoading = true; isFeedLoading = true;
isRegistered = false; isRegistered = false;
lastAckedOfferToUserJwtId?: string; // the last JWT ID for offer-to-user that they've acknowledged
numNewOffersToUser: number = 0; // number of new offers-to-user
searchBoxes: Array<{ searchBoxes: Array<{
name: string; name: string;
bbox: BoundingBox; bbox: BoundingBox;
@ -447,10 +476,11 @@ export default class HomeView extends Vue {
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible; this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby; this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isRegistered = !!settings.isRegistered; this.isRegistered = !!settings.isRegistered;
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId;
this.searchBoxes = settings.searchBoxes || []; this.searchBoxes = settings.searchBoxes || [];
this.showShortcutBvc = !!settings.showShortcutBvc; this.showShortcutBvc = !!settings.showShortcutBvc;
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings); this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
if (!settings.finishedOnboarding) { if (!settings.finishedOnboarding) {
(this.$refs.onboardingDialog as OnboardingDialog).open( (this.$refs.onboardingDialog as OnboardingDialog).open(
@ -480,6 +510,12 @@ export default class HomeView extends Vue {
// this returns a Promise but we don't need to wait for it // this returns a Promise but we don't need to wait for it
this.updateAllFeed(); this.updateAllFeed();
if (this.activeDid) {
this.numNewOffersToUser =
(await getNewOffersToUser(this.axios, this.apiServer, this.activeDid, this.lastAckedOfferToUserJwtId))
.length;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) { } catch (err: any) {
console.error("Error retrieving settings or feed.", err); console.error("Error retrieving settings or feed.", err);
@ -519,7 +555,7 @@ export default class HomeView extends Vue {
const settings = await retrieveSettingsForActiveAccount(); const settings = await retrieveSettingsForActiveAccount();
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible; this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby; this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings); this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
this.feedData = []; this.feedData = [];
this.feedPreviousOldestId = undefined; this.feedPreviousOldestId = undefined;
@ -782,6 +818,10 @@ export default class HomeView extends Vue {
} }
} }
goToActivityToUserPage() {
(this.$router as Router).push({ name: "new-activity" });
}
onClickLoadClaim(jwtId: string) { onClickLoadClaim(jwtId: string) {
const route = { const route = {
path: "/claim/" + encodeURIComponent(jwtId), path: "/claim/" + encodeURIComponent(jwtId),
@ -820,7 +860,7 @@ export default class HomeView extends Vue {
(this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange); (this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange);
} }
toastUser(message) { toastUser(message: string) {
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",

111
src/views/NewActivityView.vue

@ -0,0 +1,111 @@
<template>
<QuickNav selected="Home"></QuickNav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7" >
<!-- Back -->
<fa
icon="chevron-left"
@click="$router.back()"
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1"
/>
New Activity For You
</h1>
</div>
<!-- Display a single row with the name of "New Offers To You" with a count. -->
<div class="new-offers-row flex justify-between items-center mb-4">
<div class="flex justify-between items-center">
<span class="text-lg font-medium">New Offers To You</span>
<fa
:icon="showOffersDetails ? 'chevron-down' : 'chevron-right'"
class="cursor-pointer ml-4"
@click="showOffersDetails = !showOffersDetails"
/>
</div>
<span class="text-lg font-medium">{{ newOffersToUser.length }}</span>
</div>
<div v-if="showOffersDetails" class="ml-4">
<ul>
<li v-for="offer in newOffersToUser" :key="offer.id" class="mt-2">
<span>{{ didInfo(offer.offeredByDid, activeDid, allMyDids, allContacts) }}</span>
offers
<span v-if="offer.description">{{ offer.description }}</span>
<span v-if="offer.amount">{{ displayAmount(offer.unit, offer.amount) }}</span>
<router-link :to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }" class="text-blue-500">
<fa icon="file-lines" class="pl-2 text-blue-500 cursor-pointer" />
</router-link>
</li>
</ul>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import GiftedDialog from "@/components/GiftedDialog.vue";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { didInfo,displayAmount, getNewOffersToUser, OfferSummaryRecord } from "@/libs/endorserServer";
@Component({
components: { GiftedDialog, QuickNav, EntityIcon },
})
export default class NewActivityView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
activeDid = "";
allContacts: Array<Contact> = [];
allMyDids: string[] = [];
apiServer = "";
lastAckedOfferToUserJwtId = "";
newOffersToUser: Array<OfferSummaryRecord> = [];
showOffersDetails = false;
didInfo = didInfo;
displayAmount = displayAmount;
async created() {
try {
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId || "";
this.allContacts = await db.contacts.toArray();
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
if (allAccounts.length > 0) {
this.allMyDids = allAccounts.map((acc) => acc.did);
}
this.newOffersToUser =
await getNewOffersToUser(this.axios, this.apiServer, this.activeDid, this.lastAckedOfferToUserJwtId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
console.error("Error retrieving settings & contacts:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text:
err.message ||
"There was an error retrieving your activity.",
},
5000,
);
}
}
}
</script>
Loading…
Cancel
Save