Compare commits

..

8 Commits

Author SHA1 Message Date
Jose Olarte III
a9d9df32e1 WIP: QuickNav notification badges
- Cleanup of unused mockups
- Minor tweaks
2025-09-26 21:48:53 +08:00
Jose Olarte III
e655082af6 WIP: sticky tabs 2025-09-25 21:54:13 +08:00
Jose Olarte III
ea2fa30903 WIP: alternative notification UI 2025-09-24 21:19:22 +08:00
Jose Olarte III
39dbbb08f7 WIP: HomeView notification badge 2025-09-22 22:25:38 +08:00
Jose Olarte III
eb21d3c247 WIP: notification system adjustments
- Re-organize tabs
- Remove unneeded "Unread only" toggle (limiting functionality to chronological isUnread)
- Added "read line"
2025-09-19 23:41:03 +08:00
Jose Olarte III
213f5f0555 Merge branch 'master' into notification-system 2025-09-19 16:38:15 +08:00
Jose Olarte III
2db2c39830 WIP: notification view improvements
- Notification count badge per tab
- "Unread only" filter toggle
- Notification dot size adjustment
2025-09-17 22:13:34 +08:00
Jose Olarte III
106cefab51 WIP: notification system redesign
- Tabbed interface to expand the view's capabilities
- Added controls for managing notifications individually or in bulk
- Streamlined list design for increased information density
2025-09-15 21:43:39 +08:00
10 changed files with 329 additions and 246 deletions

View File

@@ -293,7 +293,7 @@ const inputImageFileNameRef = ref<Blob>();
export default class ImageMethodDialog extends Vue { export default class ImageMethodDialog extends Vue {
$notify!: NotifyFunction; $notify!: NotifyFunction;
$router!: Router; $router!: Router;
notify!: ReturnType<typeof createNotifyHelpers>; notify = createNotifyHelpers(this.$notify);
/** Active DID for user authentication */ /** Active DID for user authentication */
activeDid = ""; activeDid = "";
@@ -498,9 +498,6 @@ export default class ImageMethodDialog extends Vue {
* @throws {Error} When settings retrieval fails * @throws {Error} When settings retrieval fails
*/ */
async mounted() { async mounted() {
// Initialize notification helpers
this.notify = createNotifyHelpers(this.$notify);
try { try {
// Get activeDid from active_identity table (single source of truth) // Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@@ -14,11 +14,20 @@
'text-slate-500': selected !== 'Home', 'text-slate-500': selected !== 'Home',
}" }"
> >
<router-link :to="{ name: 'home' }" class="block text-center py-2 px-1"> <router-link
:to="{ name: 'home' }"
class="relative block text-center py-2 px-1"
>
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<font-awesome icon="house-chimney" class="fa-fw" /> <font-awesome icon="house-chimney" class="fa-fw" />
<span class="text-xs mt-1">feed</span> <span class="text-xs mt-1">feed</span>
</div> </div>
<!-- Notification dot - show while the user has unread notifications -->
<font-awesome
icon="circle"
class="absolute left-1/2 top-1 translate-x-2 text-rose-500 text-[10px] border border-white rounded-full"
></font-awesome>
</router-link> </router-link>
</li> </li>
<!-- Search --> <!-- Search -->
@@ -89,7 +98,7 @@
> >
<router-link <router-link
:to="{ name: 'account' }" :to="{ name: 'account' }"
class="block text-center py-2 px-1" class="relative block text-center py-2 px-1"
> >
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<font-awesome icon="circle-user" class="fa-fw" /> <font-awesome icon="circle-user" class="fa-fw" />
@@ -102,6 +111,12 @@
--> -->
<span class="text-xs mt-1">profile</span> <span class="text-xs mt-1">profile</span>
</div> </div>
<!-- Notification dot - show while the user has not yet backed up their seed phrase -->
<font-awesome
icon="circle"
class="absolute left-1/2 top-1 translate-x-2 text-rose-500 text-[10px] border border-white rounded-full"
></font-awesome>
</router-link> </router-link>
</li> </li>
</ul> </ul>

View File

@@ -80,6 +80,7 @@ import {
faQuestion, faQuestion,
faRightFromBracket, faRightFromBracket,
faRotate, faRotate,
faScroll,
faShareNodes, faShareNodes,
faSpinner, faSpinner,
faSquare, faSquare,
@@ -169,6 +170,7 @@ library.add(
faQrcode, faQrcode,
faQuestion, faQuestion,
faRotate, faRotate,
faScroll,
faRightFromBracket, faRightFromBracket,
faShareNodes, faShareNodes,
faSpinner, faSpinner,

View File

@@ -394,7 +394,7 @@ export default class HelpNotificationsView extends Vue {
notifyingReminderTime = ""; notifyingReminderTime = "";
// Notification helper system // Notification helper system
notify!: ReturnType<typeof createNotifyHelpers>; notify = createNotifyHelpers(this.$notify);
/** /**
* Computed property for consistent button styling * Computed property for consistent button styling
@@ -430,9 +430,6 @@ export default class HelpNotificationsView extends Vue {
* Handles errors gracefully with proper logging without exposing sensitive data. * Handles errors gracefully with proper logging without exposing sensitive data.
*/ */
async mounted() { async mounted() {
// Initialize notification helpers
this.notify = createNotifyHelpers(this.$notify);
try { try {
const registration = await navigator.serviceWorker?.ready; const registration = await navigator.serviceWorker?.ready;
const fullSub = await registration?.pushManager.getSubscription(); const fullSub = await registration?.pushManager.getSubscription();

View File

@@ -80,53 +80,51 @@ Raymer * @version 1.0.0 */
</router-link> </router-link>
</div> </div>
<div class="mb-8"> <!--
<!-- They should have an identifier, even if it's an auto-generated one that they'll never use.
They should have an identifier, even if it's an auto-generated one that they'll never use. Identity creation is now handled by router navigation guard.
Identity creation is now handled by router navigation guard. -->
--> <div class="mb-6">
<div class="mb-4"> <RegistrationNotice
<RegistrationNotice v-if="!isUserRegistered"
v-if="!isUserRegistered" :passkeys-enabled="PASSKEYS_ENABLED"
:passkeys-enabled="PASSKEYS_ENABLED" :given-name="givenName"
:given-name="givenName" message="To share, someone must register you."
message="To share, someone must register you." />
/>
<div v-if="isUserRegistered" id="sectionRecordSomethingGiven"> <div v-if="isUserRegistered" id="sectionRecordSomethingGiven">
<!-- Record Quick-Action --> <!-- Record Quick-Action -->
<div class="mb-6"> <div class="bg-slate-200 rounded-lg overflow-hidden p-3 pt-2.5">
<div class="flex gap-2 items-center mb-2"> <div class="flex gap-2 items-center mb-2">
<h2 class="text-xl font-bold">Record something given by:</h2> <h2 class="font-bold">Record something given by:</h2>
<button <button
class="block ms-auto text-center text-white bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-2 rounded-full" class="block ms-auto text-center text-white bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-1.5 rounded-full"
@click="openGiftedPrompts()" @click="openGiftedPrompts()"
> >
<font-awesome <font-awesome
icon="lightbulb" icon="lightbulb"
class="block text-center w-[1em]" class="block text-center text-sm w-[1em]"
/> />
</button> </button>
</div> </div>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<button <button
type="button" type="button"
class="text-center text-base uppercase 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-3 py-2 rounded-lg" class="text-center text-base uppercase 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-3 py-2 rounded-md"
@click="openPersonDialog()" @click="openPersonDialog()"
> >
<font-awesome icon="user" /> <font-awesome icon="user" />
Person Person
</button> </button>
<button <button
type="button" type="button"
class="text-center text-base uppercase 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-3 py-2 rounded-lg" class="text-center text-base uppercase 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-3 py-2 rounded-md"
@click="openProjectDialog()" @click="openProjectDialog()"
> >
<font-awesome icon="folder-open" /> <font-awesome icon="folder-open" />
Project Project
</button> </button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -138,74 +136,90 @@ Raymer * @version 1.0.0 */
:recipient-entity-type="'person'" :recipient-entity-type="'person'"
/> />
<GiftedPrompts ref="giftedPrompts" /> <GiftedPrompts ref="giftedPrompts" />
<FeedFilters ref="feedFilters" />
<!-- Results List --> <!-- Results List -->
<div class="mt-4 mb-4"> <div class="mt-4 mb-4">
<div class="flex gap-2 items-center mb-3"> <!-- ALTERNATIVE UI: Feed + Notification Tabs -->
<h2 class="text-xl font-bold">Latest Activity</h2> <div
class="sticky top-0 z-50 grid grid-cols-5 text-xl sm:text-2xl pt-4 pb-1 px-1 -mt-3 -mx-1 mb-4 bg-white rounded-b-[10px]"
>
<button <button
v-if="resultsAreFiltered()" class="relative text-center bg-slate-400 text-white px-1 pt-3 pb-2 first:rounded-s-md last:rounded-e-md border-r border-slate-300 last:border-r-0 leading-none"
class="block ms-auto text-center text-white bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-2 rounded-full"
@click="openFeedFilters()"
> >
<font-awesome <font-awesome icon="scroll" />
icon="filter" <div class="text-xs sm:text-sm mt-1">activity</div>
class="block text-center w-[1em] translate-y-[0.05em]"
/>
</button> </button>
<button <button
v-else class="relative text-center bg-slate-200 text-slate-500 px-1 pt-3 pb-2 first:rounded-s-md last:rounded-e-md border-r border-slate-300 last:border-r-0 leading-none"
class="block ms-auto text-center text-white bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-2 rounded-full"
@click="openFeedFilters()"
> >
<font-awesome <font-awesome icon="hand-holding-heart" />
icon="filter" <div class="text-xs sm:text-sm mt-1">offers</div>
class="block text-center w-[1em] translate-y-[0.05em]"
/> <!-- Unread count -->
<span
class="absolute -top-2 -translate-x-1/2 bg-rose-500 text-white border border-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em]"
>2</span
>
</button>
<button
class="relative text-center bg-slate-200 text-slate-500 px-1 pt-3 pb-2 first:rounded-s-md last:rounded-e-md border-r border-slate-300 last:border-r-0 leading-none"
>
<font-awesome icon="folder-open" />
<div class="text-xs sm:text-sm mt-1">projects</div>
<!-- Unread count -->
<span
class="absolute -top-2 -translate-x-1/2 bg-rose-500 text-white border border-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em]"
>50+</span
>
</button>
<button
class="relative text-center bg-slate-200 text-slate-500 px-1 pt-3 pb-2 first:rounded-s-md last:rounded-e-md border-r border-slate-300 last:border-r-0 leading-none"
>
<font-awesome icon="users" />
<div class="text-xs sm:text-sm mt-1">people</div>
<!-- Unread count -->
<span
class="absolute -top-2 -translate-x-1/2 bg-rose-500 text-white border border-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em]"
>4</span
>
</button>
<button
class="relative text-center bg-slate-200 text-slate-500 px-1 pt-3 pb-2 first:rounded-s-md last:rounded-e-md border-r border-slate-300 last:border-r-0 leading-none"
>
<font-awesome icon="image" />
<div class="text-xs sm:text-sm mt-1">items</div>
<!-- Unread count -->
<span
class="absolute -top-2 -translate-x-1/2 bg-rose-500 text-white border border-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em]"
>7</span
>
</button> </button>
</div> </div>
<div <div class="flex gap-2 items-center justify-between mb-2 text-sm">
class="border-t p-2 border-slate-300" <h2 class="text-base font-bold">Latest Activity</h2>
@click="goToActivityToUserPage()" <button
> v-if="resultsAreFiltered()"
<div class="flex justify-center"> class="flex items-center justify-end gap-2 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 rounded"
<div @click="openFeedFilters()"
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)] m-1 px-4 py-4 rounded-md text-white" Filter
> <font-awesome icon="filter"></font-awesome>
<span </button>
class="block text-center text-6xl" <button
data-testId="newDirectOffersActivityNumber" v-else
> class="flex items-center justify-end gap-2 bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1 rounded"
{{ numNewOffersToUser }}{{ newOffersToUserHitLimit ? "+" : "" }} @click="openFeedFilters()"
</span> >
<p class="text-center"> Filter
new offer{{ numNewOffersToUser === 1 ? "" : "s" }} to you <font-awesome icon="filter"></font-awesome>
</p> </button>
</div>
<div
v-if="numNewOffersToUserProjects"
class="bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] m-1 px-4 py-4 rounded-md text-white"
>
<span
class="block text-center text-6xl"
data-testId="newOffersToUserProjectsActivityNumber"
>
{{ numNewOffersToUserProjects
}}{{ newOffersToUserProjectsHitLimit ? "+" : "" }}
</span>
<p class="text-center">
new offer{{ numNewOffersToUserProjects === 1 ? "" : "s" }} to your
projects
</p>
</div>
</div>
<div class="flex justify-end mt-2">
<button class="text-blue-500">View All New Activity For You</button>
</div>
</div> </div>
<FeedFilters ref="feedFilters" />
<InfiniteScroll @reached-bottom="loadMoreGives"> <InfiniteScroll @reached-bottom="loadMoreGives">
<ul id="listLatestActivity" class="space-y-4"> <ul id="listLatestActivity" class="space-y-4">
<ActivityListItem <ActivityListItem

View File

@@ -3,108 +3,198 @@
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb --> <!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8"> <div class="mb-2">
<h1 class="text-lg text-center font-light relative px-7"> <h1 class="text-2xl text-center font-semibold relative px-7">
<!-- Back --> <!-- Back -->
<font-awesome <font-awesome
icon="chevron-left" icon="chevron-left"
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1" class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 top-[0.2em]"
@click="$router.back()" @click="$router.back()"
/> />
New Activity For You
Notifications
</h1> </h1>
</div> </div>
<!-- Display a single row with the name of "New Offers To You" with a count. --> <!-- Main Tabs -->
<div class="flex justify-between" data-testId="showOffersToUser"> <div class="text-center text-slate-500 border-b border-slate-300 mt-4 mb-2">
<div> <ul class="flex flex-wrap justify-center gap-4 -mb-px">
<span class="text-lg font-medium" <li class="flex items-center gap-[0.175em]">
>{{ newOffersToUser.length <a
}}{{ newOffersToUserHitLimit ? "+" : "" }}</span href="#"
> class="inline-block py-2 rounded-t-lg border-b-2 active text-black border-black font-semibold"
<span class="text-lg font-medium ml-4" >
>New Offer{{ newOffersToUser.length === 1 ? "" : "s" }} To You</span Offers
> </a>
<font-awesome
v-if="newOffersToUser.length > 0"
:icon="showOffersDetails ? 'chevron-down' : 'chevron-right'"
class="cursor-pointer ml-4 mr-4 text-lg"
@click="expandOffersToUserAndMarkRead()"
/>
</div>
<a class="text-blue-500 cursor-pointer" @click="handleSeeAllOffersToUser">
See&nbsp;all
</a>
</div>
<div v-if="showOffersDetails" class="ml-4 mt-4"> <!-- Unread count -->
<ul class="list-disc ml-4"> <span
<li class="inline-block bg-rose-500 text-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em] -me-1.5 -mt-[2px]"
v-for="offer in newOffersToUser" >3</span
:key="offer.jwtId"
class="mt-4 relative group"
>
<span>{{
didInfo(offer.offeredByDid, activeDid, allMyDids, allContacts)
}}</span>
offered
<span v-if="offer.objectDescription">{{
offer.objectDescription
}}</span
>{{ offer.objectDescription && offer.amount ? ", and " : "" }}
<span v-if="offer.amount">{{
displayAmount(offer.unit, offer.amount)
}}</span>
<router-link
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
class="text-blue-500"
> >
<font-awesome </li>
icon="file-lines" <li class="flex items-center gap-[0.175em]">
class="pl-2 text-blue-500 cursor-pointer" <a
/> href="#"
</router-link> class="inline-block py-2 rounded-t-lg border-b-2 text-blue-600 border-transparent hover:border-slate-400"
<!-- New line that appears on hover or when the offer is clicked -->
<div
class="absolute left-0 w-full text-left text-gray-500 text-sm hidden group-hover:flex cursor-pointer items-center"
@click="markOffersAsReadStartingWith(offer.jwtId)"
> >
<span class="inline-block w-8 h-px bg-gray-500 mr-2" /> Projects
Click to keep all above as new offers </a>
</div>
<!-- Unread count -->
<span
class="inline-block bg-rose-500 text-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em] -me-1.5 -mt-[2px]"
>9+</span
>
</li>
<li class="flex items-center gap-[0.175em]">
<a
href="#"
class="inline-block py-2 rounded-t-lg border-b-2 text-blue-600 border-transparent hover:border-slate-400"
>
People
</a>
</li>
<li class="flex items-center gap-[0.175em]">
<a
href="#"
class="inline-block py-2 rounded-t-lg border-b-2 text-blue-600 border-transparent hover:border-slate-400"
>
Items
</a>
</li> </li>
</ul> </ul>
</div> </div>
<!-- Display a single row with the name of "New Offers To Your Projects" with a count. --> <!-- Sub Tabs - Offers -->
<div <div class="text-center text-slate-500 border-b border-slate-300 mb-2">
class="mt-4 flex justify-between" <ul class="flex flex-wrap justify-center gap-4 text-sm -mb-px">
data-testId="showOffersToUserProjects" <li class="flex items-center gap-[0.175em]">
> <a
<div> href="#"
<span class="text-lg font-medium" class="inline-block py-2 rounded-t-lg border-b-2 active text-black border-black font-semibold"
>{{ newOffersToUserProjects.length >
}}{{ newOffersToUserProjectsHitLimit ? "+" : "" }}</span To You
</a>
<!-- Unread count -->
<span
class="inline-block bg-rose-500 text-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em] -me-1.5 -mt-[2px]"
>2</span
>
</li>
<li class="flex items-center gap-[0.175em]">
<a
href="#"
class="inline-block py-2 rounded-t-lg border-b-2 text-blue-600 border-transparent hover:border-slate-400"
>
Your Projects
</a>
</li>
<li class="flex items-center gap-[0.175em]">
<a
href="#"
class="inline-block py-2 rounded-t-lg border-b-2 text-blue-600 border-transparent hover:border-slate-400"
>
Favorites
</a>
</li>
</ul>
</div>
<!-- Offers to You -->
<div v-if="showOffersDetails" class="mt-4">
<div class="flex justify-end items-center text-sm mb-2">
<a
href="#"
class="flex items-center justify-end gap-2 bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1 rounded"
> >
<span class="text-lg font-medium ml-4" Mark all as read
>New Offer{{ newOffersToUserProjects.length === 1 ? "" : "s" }} To <font-awesome icon="check"></font-awesome>
Your Projects</span </a>
>
<font-awesome
v-if="newOffersToUserProjects.length > 0"
:icon="
showOffersToUserProjectsDetails ? 'chevron-down' : 'chevron-right'
"
class="cursor-pointer ml-4 mr-4 text-lg"
@click="expandOffersToUserProjectsAndMarkRead()"
/>
</div> </div>
<a <ul class="text-sm border-t border-slate-300">
class="text-blue-500 cursor-pointer" <li
@click="handleSeeAllOffersToUserProjects" v-for="offer in newOffersToUser"
> :key="offer.jwtId"
See&nbsp;all class="flex justify-between items-center gap-4 border-b border-slate-300 py-2"
</a> >
<router-link
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
class="block"
>
<span class="font-semibold">{{
didInfo(offer.offeredByDid, activeDid, allMyDids, allContacts)
}}</span>
offered
<span
v-if="offer.objectDescription"
class="font-semibold text-blue-600"
>{{ offer.objectDescription }}</span
>{{ offer.objectDescription && offer.amount ? " and " : "" }}
<span v-if="offer.amount" class="font-semibold text-blue-600">{{
displayAmount(offer.unit, offer.amount)
}}</span>
</router-link>
<!-- Unread indicator -->
<font-awesome
icon="circle"
class="text-rose-500 text-[8px] border border-rose-500 rounded-full"
></font-awesome>
</li>
<!-- Sample read item -->
<li class="border-b border-slate-300 py-2">
<!-- Last viewed separator -->
<div
class="border-t border-dashed border-slate-300 text-orange-400 mt-4 mb-2 font-bold text-sm"
>
<span class="block w-fit mx-auto -mt-2.5 bg-white px-2">
You've already seen all the following
</span>
<hr class="border-slate-300 mt-4" />
</div>
<div class="flex justify-between items-center gap-4">
<!-- Notification details -->
<a href="#" class="block text-slate-400">
<span class="font-semibold">User One</span>
offered
<span class="font-semibold">Sample read notification item</span>
and
<span class="font-semibold">50 USD</span>
</a>
<!-- Read indicator -->
<font-awesome
icon="circle"
class="text-transparent text-[8px] border border-slate-300 rounded-full"
></font-awesome>
</div>
</li>
<!-- Sample read item -->
<li class="border-b border-slate-300 py-2">
<div class="flex justify-between items-center gap-4">
<!-- Notification details -->
<a href="#" class="block text-slate-400">
<span class="font-semibold">User One</span>
offered
<span class="font-semibold">Sample read notification item</span>
and
<span class="font-semibold">50 USD</span>
</a>
<!-- Read indicator -->
<font-awesome
icon="circle"
class="text-transparent text-[8px] border border-slate-300 rounded-full"
></font-awesome>
</div>
</li>
</ul>
</div> </div>
<div v-if="showOffersToUserProjectsDetails" class="ml-4 mt-4"> <div v-if="showOffersToUserProjectsDetails" class="ml-4 mt-4">
@@ -194,7 +284,7 @@ export default class NewActivityView extends Vue {
newOffersToUserProjects: Array<OfferToPlanSummaryRecord> = []; newOffersToUserProjects: Array<OfferToPlanSummaryRecord> = [];
newOffersToUserProjectsHitLimit = false; newOffersToUserProjectsHitLimit = false;
showOffersDetails = false; showOffersDetails = true;
showOffersToUserProjectsDetails = false; showOffersToUserProjectsDetails = false;
didInfo = didInfo; didInfo = didInfo;
displayAmount = displayAmount; displayAmount = displayAmount;
@@ -249,7 +339,7 @@ export default class NewActivityView extends Vue {
async expandOffersToUserAndMarkRead() { async expandOffersToUserAndMarkRead() {
this.showOffersDetails = !this.showOffersDetails; this.showOffersDetails = !this.showOffersDetails;
if (this.showOffersDetails && this.newOffersToUser.length > 0) { if (this.showOffersDetails) {
await this.$updateSettings({ await this.$updateSettings({
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId, lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
}); });
@@ -286,10 +376,7 @@ export default class NewActivityView extends Vue {
async expandOffersToUserProjectsAndMarkRead() { async expandOffersToUserProjectsAndMarkRead() {
this.showOffersToUserProjectsDetails = this.showOffersToUserProjectsDetails =
!this.showOffersToUserProjectsDetails; !this.showOffersToUserProjectsDetails;
if ( if (this.showOffersToUserProjectsDetails) {
this.showOffersToUserProjectsDetails &&
this.newOffersToUserProjects.length > 0
) {
await this.$updateSettings({ await this.$updateSettings({
lastAckedOfferToUserProjectsJwtId: lastAckedOfferToUserProjectsJwtId:
this.newOffersToUserProjects[0].jwtId, this.newOffersToUserProjects[0].jwtId,
@@ -297,7 +384,7 @@ export default class NewActivityView extends Vue {
// note that we don't update this.lastAckedOfferToUserProjectsJwtId in case // note that we don't update this.lastAckedOfferToUserProjectsJwtId in case
// they later choose the last one to keep the offers as new // they later choose the last one to keep the offers as new
this.notify.info( this.notify.info(
"The offers are marked as viewed. Click in the list to keep them as new.", "The offers are now marked as viewed. Click in the list to keep them as new.",
TIMEOUTS.LONG, TIMEOUTS.LONG,
); );
} }
@@ -325,13 +412,5 @@ export default class NewActivityView extends Vue {
TIMEOUTS.STANDARD, TIMEOUTS.STANDARD,
); );
} }
async handleSeeAllOffersToUser() {
this.$router.push("/recent-offers-to-user");
}
async handleSeeAllOffersToUserProjects() {
this.$router.push("/recent-offers-to-user-projects");
}
} }
</script> </script>

View File

@@ -99,7 +99,7 @@ export default class QuickActionBvcBeginView extends Vue {
$router!: Router; $router!: Router;
// Notification helper system // Notification helper system
private notify!: ReturnType<typeof createNotifyHelpers>; private notify = createNotifyHelpers(this.$notify);
attended = true; attended = true;
gaveTime = true; gaveTime = true;
@@ -111,9 +111,6 @@ export default class QuickActionBvcBeginView extends Vue {
* Uses America/Denver timezone for Bountiful location * Uses America/Denver timezone for Bountiful location
*/ */
async mounted() { async mounted() {
// Initialize notification helpers
this.notify = createNotifyHelpers(this.$notify);
logger.debug( logger.debug(
"[QuickActionBvcBeginView] Mounted - calculating meeting date", "[QuickActionBvcBeginView] Mounted - calculating meeting date",
); );

View File

@@ -32,20 +32,20 @@
</div> </div>
<InfiniteScroll @reached-bottom="loadMoreOffersToUserProjects"> <InfiniteScroll @reached-bottom="loadMoreOffersToUserProjects">
<ul data-testId="listRecentOffersToUserProjects"> <ul
data-testId="listRecentOffersToUserProjects"
class="border-t border-slate-300"
>
<li <li
v-for="offer in newOffersToUserProjects" v-for="offer in newOffersToUserProjects"
:key="offer.jwtId" :key="offer.jwtId"
class="mt-4 relative group" class="mt-4 relative group"
> >
<!-- Last viewed separator -->
<div <div
v-if="offer.jwtId == lastAckedOfferToUserProjectsJwtId" v-if="offer.jwtId == lastAckedOfferToUserProjectsJwtId"
class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-6 font-bold text-sm" class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
> >
<span class="block w-fit mx-auto -mb-2.5 bg-white px-2"> You've already seen all the following
You've already seen all the following
</span>
</div> </div>
<span>{{ <span>{{
@@ -147,14 +147,6 @@ export default class RecentOffersToUserView extends Vue {
this.newOffersToUserProjects = offersToUserProjectsData.data; this.newOffersToUserProjects = offersToUserProjectsData.data;
this.newOffersToUserProjectsAtEnd = !offersToUserProjectsData.hitLimit; this.newOffersToUserProjectsAtEnd = !offersToUserProjectsData.hitLimit;
// Mark offers as read after data is loaded
if (this.newOffersToUserProjects.length > 0) {
await this.$updateSettings({
lastAckedOfferToUserProjectsJwtId:
this.newOffersToUserProjects[0].jwtId,
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) { } catch (err: any) {
logger.error("Error retrieving settings & contacts:", err); logger.error("Error retrieving settings & contacts:", err);

View File

@@ -27,20 +27,20 @@
</p> </p>
</div> </div>
<InfiniteScroll @reached-bottom="loadMoreOffersToUser"> <InfiniteScroll @reached-bottom="loadMoreOffersToUser">
<ul data-testId="listRecentOffersToUser"> <ul
data-testId="listRecentOffersToUser"
class="border-t border-slate-300"
>
<li <li
v-for="offer in newOffersToUser" v-for="offer in newOffersToUser"
:key="offer.jwtId" :key="offer.jwtId"
class="mt-4 relative group" class="mt-4 relative group"
> >
<!-- Last viewed separator -->
<div <div
v-if="offer.jwtId == lastAckedOfferToUserJwtId" v-if="offer.jwtId == lastAckedOfferToUserJwtId"
class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-6 font-bold text-sm" class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
> >
<span class="block w-fit mx-auto -mb-2.5 bg-white px-2"> You've already seen all the following
You've already seen all the following
</span>
</div> </div>
<span>{{ <span>{{
@@ -138,13 +138,6 @@ export default class RecentOffersToUserView extends Vue {
this.newOffersToUser = offersToUserData.data; this.newOffersToUser = offersToUserData.data;
this.newOffersToUserAtEnd = !offersToUserData.hitLimit; this.newOffersToUserAtEnd = !offersToUserData.hitLimit;
// Mark offers as read after data is loaded
if (this.newOffersToUser.length > 0) {
await this.$updateSettings({
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) { } catch (err: any) {
logger.error("Error retrieving settings & contacts:", err); logger.error("Error retrieving settings & contacts:", err);

View File

@@ -162,7 +162,7 @@ export default class SeedBackupView extends Vue {
showSeed = false; showSeed = false;
// Notification helper system // Notification helper system
notify!: ReturnType<typeof createNotifyHelpers>; notify = createNotifyHelpers(this.$notify);
/** /**
* Computed property for consistent copy feedback styling * Computed property for consistent copy feedback styling
@@ -204,9 +204,6 @@ export default class SeedBackupView extends Vue {
* Handles errors gracefully with user notifications. * Handles errors gracefully with user notifications.
*/ */
async created() { async created() {
// Initialize notification helpers
this.notify = createNotifyHelpers(this.$notify);
try { try {
let activeDid = ""; let activeDid = "";