Compare commits

..

12 Commits

Author SHA1 Message Date
f4569d8b98 Merge branch 'master' into load-build-mode-env-file 2025-09-19 04:35:05 -04:00
7575895f75 Merge pull request 'fix: initialize notification helpers in lifecycle methods' (#200) from notify-initialization-fix into master
Reviewed-on: #200
2025-09-19 03:56:49 -04:00
67a9ecf6c6 Merge branch 'master' into notify-initialization-fix 2025-09-19 03:56:24 -04:00
823fa51275 Merge pull request 'feat(NewActivityView): enhance "See all" links to mark offers as read before navigation' (#198) from new-activity-mark-read into master
Reviewed-on: #198
2025-09-19 03:14:23 -04:00
Jose Olarte III
e2c2d54c20 Merge branch 'master' into new-activity-mark-read 2025-09-19 15:14:42 +08:00
Jose Olarte III
6fd53b020e refactor: simplify notification messages for offer viewing
- Remove conditional notification logic in NewActivityView
- Remove redundant notification in RecentOffersToUserView and RecentOffersToUserProjectsView
- Standardize to single notification message format
2025-09-19 15:00:17 +08:00
Jose Olarte III
a3d6b458b1 fix: load environment-specific .env files in iOS/Android/Electron build scripts
- iOS, Android, and Electron build scripts now load .env.development, .env.test, .env.production files
- Previously only loaded generic .env file which doesn't exist
- Ensures consistent image server URL across all build targets
- Fixes issue where build:ios:dev used production image URL instead of test URL
- Aligns with web build script behavior for environment variable precedence

Resolves inconsistent VITE_DEFAULT_IMAGE_API_SERVER values between build targets.
2025-09-18 22:38:53 +08:00
Jose Olarte III
b1fcb49e7c fix: initialize notification helpers in lifecycle methods
- Fix 't is not a function' error during image upload by properly initializing notification helpers
- Move notification helper initialization from class-level to lifecycle methods (created/mounted)
- Affected components: ImageMethodDialog, SeedBackupView, QuickActionBvcBeginView, HelpNotificationsView
- Ensures $notify is available when createNotifyHelpers() is called
- Resolves notification errors in image upload functionality
2025-09-18 21:42:15 +08:00
Jose Olarte III
5d9f455fc8 feat: move mark-as-read logic from navigation to view loading
- Remove mark-as-read logic from NewActivityView navigation handlers
- Add mark-as-read logic to RecentOffersToUserView and RecentOffersToUserProjectsView after data loading
- Improve "You've already seen all the following" marker positioning
- Update marker styling with dashed border and centered text

This ensures the marker appears at the correct position in the list
instead of always at the top, providing better UX when viewing offers.
2025-09-16 18:10:17 +08:00
c3ff471ea1 Merge branch 'master' into new-activity-mark-read 2025-09-16 04:19:17 -04:00
Jose Olarte III
ac603f66e2 Lint fix 2025-09-10 18:19:40 +08:00
Jose Olarte III
9bdd66b9c9 feat(NewActivityView): enhance "See all" links to mark offers as read before navigation
- Replace router-links with click handlers for both "See all" offers links
- Add handleSeeAllOffersToUser and handleSeeAllOffersToUserProjects methods
- Modify expandOffersToUserAndMarkRead to accept fromSeeAll parameter for contextual notifications
- Modify expandOffersToUserProjectsAndMarkRead to accept fromSeeAll parameter for contextual notifications
- Show shorter notification messages when called from "See all" vs chevron expand buttons
- Add safety checks to prevent errors when offers arrays are empty
- Standardize notification message text consistency
- TypeScript and formatting lint fixes

Both "See all" links now properly mark offers as viewed before navigation,
preventing users from seeing unread offers in the detailed views.
2025-09-10 18:19:17 +08:00
13 changed files with 274 additions and 325 deletions

View File

@@ -351,8 +351,18 @@ fi
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Load environment-specific .env file if it exists
env_file=".env.$BUILD_MODE"
if [ -f "$env_file" ]; then
load_env_file "$env_file"
else
log_debug "No $env_file file found, using default environment"
fi
# Load .env file if it exists (fallback)
if [ -f ".env" ]; then
load_env_file ".env"
fi
# Handle clean-only mode
if [ "$CLEAN_ONLY" = true ]; then

View File

@@ -341,7 +341,19 @@ main_electron_build() {
# Setup environment
setup_build_env "electron" "$BUILD_MODE"
setup_app_directories
load_env_file ".env"
# Load environment-specific .env file if it exists
env_file=".env.$BUILD_MODE"
if [ -f "$env_file" ]; then
load_env_file "$env_file"
else
log_debug "No $env_file file found, using default environment"
fi
# Load .env file if it exists (fallback)
if [ -f ".env" ]; then
load_env_file ".env"
fi
# Step 1: Clean Electron build artifacts
clean_electron_artifacts

View File

@@ -324,8 +324,18 @@ fi
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Load environment-specific .env file if it exists
env_file=".env.$BUILD_MODE"
if [ -f "$env_file" ]; then
load_env_file "$env_file"
else
log_debug "No $env_file file found, using default environment"
fi
# Load .env file if it exists (fallback)
if [ -f ".env" ]; then
load_env_file ".env"
fi
# Validate iOS environment
validate_ios_environment

View File

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

View File

@@ -14,20 +14,11 @@
'text-slate-500': selected !== 'Home',
}"
>
<router-link
:to="{ name: 'home' }"
class="relative block text-center py-2 px-1"
>
<router-link :to="{ name: 'home' }" class="block text-center py-2 px-1">
<div class="flex flex-col items-center">
<font-awesome icon="house-chimney" class="fa-fw" />
<span class="text-xs mt-1">feed</span>
</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>
</li>
<!-- Search -->
@@ -98,7 +89,7 @@
>
<router-link
:to="{ name: 'account' }"
class="relative block text-center py-2 px-1"
class="block text-center py-2 px-1"
>
<div class="flex flex-col items-center">
<font-awesome icon="circle-user" class="fa-fw" />
@@ -111,12 +102,6 @@
-->
<span class="text-xs mt-1">profile</span>
</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>
</li>
</ul>

View File

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

View File

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

View File

@@ -80,51 +80,53 @@ Raymer * @version 1.0.0 */
</router-link>
</div>
<!--
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.
-->
<div class="mb-6">
<RegistrationNotice
v-if="!isUserRegistered"
:passkeys-enabled="PASSKEYS_ENABLED"
:given-name="givenName"
message="To share, someone must register you."
/>
<div class="mb-8">
<!--
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.
-->
<div class="mb-4">
<RegistrationNotice
v-if="!isUserRegistered"
:passkeys-enabled="PASSKEYS_ENABLED"
:given-name="givenName"
message="To share, someone must register you."
/>
<div v-if="isUserRegistered" id="sectionRecordSomethingGiven">
<!-- Record Quick-Action -->
<div class="bg-slate-200 rounded-lg overflow-hidden p-3 pt-2.5">
<div class="flex gap-2 items-center mb-2">
<h2 class="font-bold">Record something given by:</h2>
<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-1.5 rounded-full"
@click="openGiftedPrompts()"
>
<font-awesome
icon="lightbulb"
class="block text-center text-sm w-[1em]"
/>
</button>
</div>
<div v-if="isUserRegistered" id="sectionRecordSomethingGiven">
<!-- Record Quick-Action -->
<div class="mb-6">
<div class="flex gap-2 items-center mb-2">
<h2 class="text-xl font-bold">Record something given by:</h2>
<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"
@click="openGiftedPrompts()"
>
<font-awesome
icon="lightbulb"
class="block text-center w-[1em]"
/>
</button>
</div>
<div class="grid grid-cols-2 gap-2">
<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-md"
@click="openPersonDialog()"
>
<font-awesome icon="user" />
Person
</button>
<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-md"
@click="openProjectDialog()"
>
<font-awesome icon="folder-open" />
Project
</button>
<div class="grid grid-cols-2 gap-2">
<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"
@click="openPersonDialog()"
>
<font-awesome icon="user" />
Person
</button>
<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"
@click="openProjectDialog()"
>
<font-awesome icon="folder-open" />
Project
</button>
</div>
</div>
</div>
</div>
@@ -136,90 +138,74 @@ Raymer * @version 1.0.0 */
:recipient-entity-type="'person'"
/>
<GiftedPrompts ref="giftedPrompts" />
<FeedFilters ref="feedFilters" />
<!-- Results List -->
<div class="mt-4 mb-4">
<!-- ALTERNATIVE UI: Feed + Notification Tabs -->
<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
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"
>
<font-awesome icon="scroll" />
<div class="text-xs sm:text-sm mt-1">activity</div>
</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="hand-holding-heart" />
<div class="text-xs sm:text-sm mt-1">offers</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]"
>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>
</div>
<div class="flex gap-2 items-center justify-between mb-2 text-sm">
<h2 class="text-base font-bold">Latest Activity</h2>
<div class="flex gap-2 items-center mb-3">
<h2 class="text-xl font-bold">Latest Activity</h2>
<button
v-if="resultsAreFiltered()"
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"
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()"
>
Filter
<font-awesome icon="filter"></font-awesome>
<font-awesome
icon="filter"
class="block text-center w-[1em] translate-y-[0.05em]"
/>
</button>
<button
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"
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()"
>
Filter
<font-awesome icon="filter"></font-awesome>
<font-awesome
icon="filter"
class="block text-center w-[1em] translate-y-[0.05em]"
/>
</button>
</div>
<FeedFilters ref="feedFilters" />
<div
class="border-t p-2 border-slate-300"
@click="goToActivityToUserPage()"
>
<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)] m-1 px-4 py-4 rounded-md text-white"
>
<span
class="block text-center text-6xl"
data-testId="newDirectOffersActivityNumber"
>
{{ numNewOffersToUser }}{{ newOffersToUserHitLimit ? "+" : "" }}
</span>
<p class="text-center">
new offer{{ numNewOffersToUser === 1 ? "" : "s" }} to you
</p>
</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>
<InfiniteScroll @reached-bottom="loadMoreGives">
<ul id="listLatestActivity" class="space-y-4">
<ActivityListItem

View File

@@ -3,200 +3,110 @@
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div class="mb-2">
<h1 class="text-2xl text-center font-semibold relative px-7">
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Back -->
<font-awesome
icon="chevron-left"
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 top-[0.2em]"
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1"
@click="$router.back()"
/>
Notifications
New Activity For You
</h1>
</div>
<!-- Main Tabs -->
<div class="text-center text-slate-500 border-b border-slate-300 mt-4 mb-2">
<ul class="flex flex-wrap justify-center gap-4 -mb-px">
<li class="flex items-center gap-[0.175em]">
<a
href="#"
class="inline-block py-2 rounded-t-lg border-b-2 active text-black border-black font-semibold"
>
Offers
</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]"
>3</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"
>
Projects
</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]"
>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>
</ul>
</div>
<!-- Sub Tabs - Offers -->
<div class="text-center text-slate-500 border-b border-slate-300 mb-2">
<ul class="flex flex-wrap justify-center gap-4 text-sm -mb-px">
<li class="flex items-center gap-[0.175em]">
<a
href="#"
class="inline-block py-2 rounded-t-lg border-b-2 active text-black border-black font-semibold"
>
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"
<!-- Display a single row with the name of "New Offers To You" with a count. -->
<div class="flex justify-between" data-testId="showOffersToUser">
<div>
<span class="text-lg font-medium"
>{{ newOffersToUser.length
}}{{ newOffersToUserHitLimit ? "+" : "" }}</span
>
Mark all as read
<font-awesome icon="check"></font-awesome>
</a>
<span class="text-lg font-medium ml-4"
>New Offer{{ newOffersToUser.length === 1 ? "" : "s" }} To You</span
>
<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>
<ul class="text-sm border-t border-slate-300">
<a class="text-blue-500 cursor-pointer" @click="handleSeeAllOffersToUser">
See&nbsp;all
</a>
</div>
<div v-if="showOffersDetails" class="ml-4 mt-4">
<ul class="list-disc ml-4">
<li
v-for="offer in newOffersToUser"
:key="offer.jwtId"
class="flex justify-between items-center gap-4 border-b border-slate-300 py-2"
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="block"
class="text-blue-500"
>
<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>
<font-awesome
icon="file-lines"
class="pl-2 text-blue-500 cursor-pointer"
/>
</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 -->
<!-- New line that appears on hover or when the offer is clicked -->
<div
class="border-t border-dashed border-slate-300 text-orange-400 mt-4 mb-2 font-bold text-sm"
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="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>
<span class="inline-block w-8 h-px bg-gray-500 mr-2" />
Click to keep all above as new offers
</div>
</li>
</ul>
</div>
<!-- Display a single row with the name of "New Offers To Your Projects" with a count. -->
<div
class="mt-4 flex justify-between"
data-testId="showOffersToUserProjects"
>
<div>
<span class="text-lg font-medium"
>{{ newOffersToUserProjects.length
}}{{ newOffersToUserProjectsHitLimit ? "+" : "" }}</span
>
<span class="text-lg font-medium ml-4"
>New Offer{{ newOffersToUserProjects.length === 1 ? "" : "s" }} To
Your Projects</span
>
<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>
<a
class="text-blue-500 cursor-pointer"
@click="handleSeeAllOffersToUserProjects"
>
See&nbsp;all
</a>
</div>
<div v-if="showOffersToUserProjectsDetails" class="ml-4 mt-4">
<ul class="list-disc ml-4">
<li
@@ -284,7 +194,7 @@ export default class NewActivityView extends Vue {
newOffersToUserProjects: Array<OfferToPlanSummaryRecord> = [];
newOffersToUserProjectsHitLimit = false;
showOffersDetails = true;
showOffersDetails = false;
showOffersToUserProjectsDetails = false;
didInfo = didInfo;
displayAmount = displayAmount;
@@ -339,7 +249,7 @@ export default class NewActivityView extends Vue {
async expandOffersToUserAndMarkRead() {
this.showOffersDetails = !this.showOffersDetails;
if (this.showOffersDetails) {
if (this.showOffersDetails && this.newOffersToUser.length > 0) {
await this.$updateSettings({
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
});
@@ -376,7 +286,10 @@ export default class NewActivityView extends Vue {
async expandOffersToUserProjectsAndMarkRead() {
this.showOffersToUserProjectsDetails =
!this.showOffersToUserProjectsDetails;
if (this.showOffersToUserProjectsDetails) {
if (
this.showOffersToUserProjectsDetails &&
this.newOffersToUserProjects.length > 0
) {
await this.$updateSettings({
lastAckedOfferToUserProjectsJwtId:
this.newOffersToUserProjects[0].jwtId,
@@ -384,7 +297,7 @@ export default class NewActivityView extends Vue {
// note that we don't update this.lastAckedOfferToUserProjectsJwtId in case
// they later choose the last one to keep the offers as new
this.notify.info(
"The offers are now marked as viewed. Click in the list to keep them as new.",
"The offers are marked as viewed. Click in the list to keep them as new.",
TIMEOUTS.LONG,
);
}
@@ -412,5 +325,13 @@ export default class NewActivityView extends Vue {
TIMEOUTS.STANDARD,
);
}
async handleSeeAllOffersToUser() {
this.$router.push("/recent-offers-to-user");
}
async handleSeeAllOffersToUserProjects() {
this.$router.push("/recent-offers-to-user-projects");
}
}
</script>

View File

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

View File

@@ -32,20 +32,20 @@
</div>
<InfiniteScroll @reached-bottom="loadMoreOffersToUserProjects">
<ul
data-testId="listRecentOffersToUserProjects"
class="border-t border-slate-300"
>
<ul data-testId="listRecentOffersToUserProjects">
<li
v-for="offer in newOffersToUserProjects"
:key="offer.jwtId"
class="mt-4 relative group"
>
<!-- Last viewed separator -->
<div
v-if="offer.jwtId == lastAckedOfferToUserProjectsJwtId"
class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-6 font-bold text-sm"
>
You've already seen all the following
<span class="block w-fit mx-auto -mb-2.5 bg-white px-2">
You've already seen all the following
</span>
</div>
<span>{{
@@ -147,6 +147,14 @@ export default class RecentOffersToUserView extends Vue {
this.newOffersToUserProjects = offersToUserProjectsData.data;
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
} catch (err: any) {
logger.error("Error retrieving settings & contacts:", err);

View File

@@ -27,20 +27,20 @@
</p>
</div>
<InfiniteScroll @reached-bottom="loadMoreOffersToUser">
<ul
data-testId="listRecentOffersToUser"
class="border-t border-slate-300"
>
<ul data-testId="listRecentOffersToUser">
<li
v-for="offer in newOffersToUser"
:key="offer.jwtId"
class="mt-4 relative group"
>
<!-- Last viewed separator -->
<div
v-if="offer.jwtId == lastAckedOfferToUserJwtId"
class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-6 font-bold text-sm"
>
You've already seen all the following
<span class="block w-fit mx-auto -mb-2.5 bg-white px-2">
You've already seen all the following
</span>
</div>
<span>{{
@@ -138,6 +138,13 @@ export default class RecentOffersToUserView extends Vue {
this.newOffersToUser = offersToUserData.data;
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
} catch (err: any) {
logger.error("Error retrieving settings & contacts:", err);

View File

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