forked from jsnbuchanan/crowd-funder-for-time-pwa
- 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.
333 lines
11 KiB
Vue
333 lines
11 KiB
Vue
<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 -->
|
|
<font-awesome
|
|
icon="chevron-left"
|
|
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
|
@click="$router.back()"
|
|
/>
|
|
New Activity For You
|
|
</h1>
|
|
</div>
|
|
|
|
<!-- 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
|
|
>
|
|
<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>
|
|
<a class="text-blue-500 cursor-pointer" @click="handleSeeAllOffersToUser">
|
|
See 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="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
|
|
icon="file-lines"
|
|
class="pl-2 text-blue-500 cursor-pointer"
|
|
/>
|
|
</router-link>
|
|
<!-- 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" />
|
|
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 all
|
|
</a>
|
|
</div>
|
|
|
|
<div v-if="showOffersToUserProjectsDetails" class="ml-4 mt-4">
|
|
<ul class="list-disc ml-4">
|
|
<li
|
|
v-for="offer in newOffersToUserProjects"
|
|
: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>
|
|
to
|
|
<span>{{ offer.planName }}</span>
|
|
<router-link
|
|
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
|
class="text-blue-500"
|
|
>
|
|
<font-awesome
|
|
icon="file-lines"
|
|
class="pl-2 text-blue-500 cursor-pointer"
|
|
/>
|
|
</router-link>
|
|
<!-- New line that appears on hover -->
|
|
<div
|
|
class="absolute left-0 w-full text-left text-gray-500 text-sm hidden group-hover:flex cursor-pointer items-center"
|
|
@click="markOffersToUserProjectsAsReadStartingWith(offer.jwtId)"
|
|
>
|
|
<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>
|
|
</section>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Vue } from "vue-facing-decorator";
|
|
|
|
import GiftedDialog from "../components/GiftedDialog.vue";
|
|
import QuickNav from "../components/QuickNav.vue";
|
|
import EntityIcon from "../components/EntityIcon.vue";
|
|
import { NotificationIface } from "../constants/app";
|
|
import { Contact } from "../db/tables/contacts";
|
|
import { Router } from "vue-router";
|
|
import {
|
|
OfferSummaryRecord,
|
|
OfferToPlanSummaryRecord,
|
|
} from "../interfaces/records";
|
|
import {
|
|
didInfo,
|
|
displayAmount,
|
|
getNewOffersToUser,
|
|
getNewOffersToUserProjects,
|
|
} from "../libs/endorserServer";
|
|
import { retrieveAccountDids } from "../libs/util";
|
|
import { logger } from "../utils/logger";
|
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
|
|
|
@Component({
|
|
components: { GiftedDialog, QuickNav, EntityIcon },
|
|
mixins: [PlatformServiceMixin],
|
|
})
|
|
export default class NewActivityView extends Vue {
|
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
|
$router!: Router;
|
|
|
|
notify!: ReturnType<typeof createNotifyHelpers>;
|
|
activeDid = "";
|
|
allContacts: Array<Contact> = [];
|
|
allMyDids: string[] = [];
|
|
apiServer = "";
|
|
lastAckedOfferToUserJwtId = "";
|
|
lastAckedOfferToUserProjectsJwtId = "";
|
|
newOffersToUser: Array<OfferSummaryRecord> = [];
|
|
newOffersToUserHitLimit = false;
|
|
newOffersToUserProjects: Array<OfferToPlanSummaryRecord> = [];
|
|
newOffersToUserProjectsHitLimit = false;
|
|
|
|
showOffersDetails = false;
|
|
showOffersToUserProjectsDetails = false;
|
|
didInfo = didInfo;
|
|
displayAmount = displayAmount;
|
|
|
|
async created() {
|
|
this.notify = createNotifyHelpers(this.$notify);
|
|
|
|
try {
|
|
const settings = await this.$accountSettings();
|
|
this.apiServer = settings.apiServer || "";
|
|
this.activeDid = settings.activeDid || "";
|
|
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId || "";
|
|
this.lastAckedOfferToUserProjectsJwtId =
|
|
settings.lastAckedOfferToUserProjectsJwtId || "";
|
|
|
|
this.allContacts = await this.$getAllContacts();
|
|
|
|
this.allMyDids = await retrieveAccountDids();
|
|
|
|
const offersToUserData = await getNewOffersToUser(
|
|
this.axios,
|
|
this.apiServer,
|
|
this.activeDid,
|
|
this.lastAckedOfferToUserJwtId,
|
|
);
|
|
this.newOffersToUser = offersToUserData.data;
|
|
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
|
|
|
|
const offersToUserProjectsData = await getNewOffersToUserProjects(
|
|
this.axios,
|
|
this.apiServer,
|
|
this.activeDid,
|
|
this.lastAckedOfferToUserProjectsJwtId,
|
|
);
|
|
this.newOffersToUserProjects = offersToUserProjectsData.data;
|
|
this.newOffersToUserProjectsHitLimit = offersToUserProjectsData.hitLimit;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
} catch (err: any) {
|
|
logger.error("Error retrieving settings & contacts:", err);
|
|
this.notify.error(
|
|
err.message || "There was an error retrieving your activity.",
|
|
TIMEOUTS.LONG,
|
|
);
|
|
}
|
|
}
|
|
|
|
async expandOffersToUserAndMarkRead(fromSeeAll: boolean = false) {
|
|
this.showOffersDetails = !this.showOffersDetails;
|
|
if (this.showOffersDetails && this.newOffersToUser.length > 0) {
|
|
await this.$updateSettings({
|
|
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
|
|
});
|
|
// note that we don't update this.lastAckedOfferToUserJwtId in case they
|
|
// later choose the last one to keep the offers as new
|
|
const message = fromSeeAll
|
|
? "The offers are marked as viewed."
|
|
: "The offers are marked as viewed. Click in the list to keep them as new.";
|
|
this.notify.info(message, TIMEOUTS.LONG);
|
|
}
|
|
}
|
|
|
|
async markOffersAsReadStartingWith(jwtId: string) {
|
|
const index = this.newOffersToUser.findIndex(
|
|
(offer) => offer.jwtId === jwtId,
|
|
);
|
|
if (index !== -1 && index < this.newOffersToUser.length - 1) {
|
|
// Set to the next offer's jwtId
|
|
await this.$updateSettings({
|
|
lastAckedOfferToUserJwtId: this.newOffersToUser[index + 1].jwtId,
|
|
});
|
|
} else {
|
|
// it's the last entry (or not found), so just keep it the same
|
|
await this.$updateSettings({
|
|
lastAckedOfferToUserJwtId: this.lastAckedOfferToUserJwtId,
|
|
});
|
|
}
|
|
this.notify.info(
|
|
"All offers above that line are marked as unread.",
|
|
TIMEOUTS.STANDARD,
|
|
);
|
|
}
|
|
|
|
async expandOffersToUserProjectsAndMarkRead(fromSeeAll: boolean = false) {
|
|
this.showOffersToUserProjectsDetails =
|
|
!this.showOffersToUserProjectsDetails;
|
|
if (
|
|
this.showOffersToUserProjectsDetails &&
|
|
this.newOffersToUserProjects.length > 0
|
|
) {
|
|
await this.$updateSettings({
|
|
lastAckedOfferToUserProjectsJwtId:
|
|
this.newOffersToUserProjects[0].jwtId,
|
|
});
|
|
// note that we don't update this.lastAckedOfferToUserProjectsJwtId in case
|
|
// they later choose the last one to keep the offers as new
|
|
const message = fromSeeAll
|
|
? "The offers are marked as viewed."
|
|
: "The offers are marked as viewed. Click in the list to keep them as new.";
|
|
this.notify.info(message, TIMEOUTS.LONG);
|
|
}
|
|
}
|
|
|
|
async markOffersToUserProjectsAsReadStartingWith(jwtId: string) {
|
|
const index = this.newOffersToUserProjects.findIndex(
|
|
(offer) => offer.jwtId === jwtId,
|
|
);
|
|
if (index !== -1 && index < this.newOffersToUserProjects.length - 1) {
|
|
// Set to the next offer's jwtId
|
|
await this.$updateSettings({
|
|
lastAckedOfferToUserProjectsJwtId:
|
|
this.newOffersToUserProjects[index + 1].jwtId,
|
|
});
|
|
} else {
|
|
// it's the last entry (or not found), so just keep it the same
|
|
await this.$updateSettings({
|
|
lastAckedOfferToUserProjectsJwtId:
|
|
this.lastAckedOfferToUserProjectsJwtId,
|
|
});
|
|
}
|
|
this.notify.info(
|
|
"All offers above that line are marked as unread.",
|
|
TIMEOUTS.STANDARD,
|
|
);
|
|
}
|
|
|
|
async handleSeeAllOffersToUser() {
|
|
this.$router.push("/recent-offers-to-user");
|
|
}
|
|
|
|
async handleSeeAllOffersToUserProjects() {
|
|
this.$router.push("/recent-offers-to-user-projects");
|
|
}
|
|
}
|
|
</script>
|