feat: add a notification for changes to starred projects

This commit is contained in:
2025-08-29 17:31:00 -06:00
parent da0621c09a
commit 24a7cf5eb6
6 changed files with 267 additions and 4 deletions

View File

@@ -144,6 +144,73 @@
</li>
</ul>
</div>
<!-- Starred Projects with Changes Section -->
<div
class="flex justify-between mt-6"
data-testId="showStarredProjectChanges"
>
<div>
<span class="text-lg font-medium"
>{{ newStarredProjectChanges.length
}}{{ newStarredProjectChangesHitLimit ? "+" : "" }}</span
>
<span class="text-lg font-medium ml-4"
>Starred Project{{
newStarredProjectChanges.length === 1 ? "" : "s"
}}
With Changes</span
>
<font-awesome
v-if="newStarredProjectChanges.length > 0"
:icon="
showStarredProjectChangesDetails ? 'chevron-down' : 'chevron-right'
"
class="cursor-pointer ml-4 mr-4 text-lg"
@click="expandStarredProjectChangesAndMarkRead()"
/>
</div>
</div>
<div v-if="showStarredProjectChangesDetails" class="ml-4 mt-4">
<ul class="list-disc ml-4">
<li
v-for="projectChange in newStarredProjectChanges"
:key="projectChange.plan.handleId"
class="mt-4 relative group"
>
<span class="font-medium">{{
projectChange.plan.name || "Unnamed Project"
}}</span>
<span v-if="projectChange.plan.description" class="text-gray-600">
- {{ projectChange.plan.description }}
</span>
<router-link
:to="{
path: '/plan/' + encodeURIComponent(projectChange.plan.handleId),
}"
class="text-blue-500"
>
<font-awesome
icon="external-link-alt"
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="
markStarredProjectChangesAsReadStartingWith(
projectChange.plan.handleId,
)
"
>
<span class="inline-block w-8 h-px bg-gray-500 mr-2" />
Click to keep all above as new changes
</div>
</li>
</ul>
</div>
</section>
</template>
@@ -159,17 +226,20 @@ import { Router } from "vue-router";
import {
OfferSummaryRecord,
OfferToPlanSummaryRecord,
PlanSummaryAndPreviousClaim,
} from "../interfaces/records";
import {
didInfo,
displayAmount,
getNewOffersToUser,
getNewOffersToUserProjects,
getStarredProjectsWithChanges,
} from "../libs/endorserServer";
import { retrieveAccountDids } from "../libs/util";
import { logger } from "../utils/logger";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import * as databaseUtil from "../db/databaseUtil";
@Component({
components: { GiftedDialog, QuickNav, EntityIcon },
@@ -186,13 +256,18 @@ export default class NewActivityView extends Vue {
apiServer = "";
lastAckedOfferToUserJwtId = "";
lastAckedOfferToUserProjectsJwtId = "";
lastAckedStarredProjectChangesJwtId = "";
newOffersToUser: Array<OfferSummaryRecord> = [];
newOffersToUserHitLimit = false;
newOffersToUserProjects: Array<OfferToPlanSummaryRecord> = [];
newOffersToUserProjectsHitLimit = false;
newStarredProjectChanges: Array<PlanSummaryAndPreviousClaim> = [];
newStarredProjectChangesHitLimit = false;
starredProjectIds: Array<string> = [];
showOffersDetails = false;
showOffersToUserProjectsDetails = false;
showStarredProjectChangesDetails = false;
didInfo = didInfo;
displayAmount = displayAmount;
@@ -206,6 +281,12 @@ export default class NewActivityView extends Vue {
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId || "";
this.lastAckedOfferToUserProjectsJwtId =
settings.lastAckedOfferToUserProjectsJwtId || "";
this.lastAckedStarredProjectChangesJwtId =
settings.lastAckedStarredProjectChangesJwtId || "";
this.starredProjectIds = databaseUtil.parseJsonField(
settings.starredProjectIds,
[],
);
this.allContacts = await this.$getAllContacts();
@@ -229,6 +310,26 @@ export default class NewActivityView extends Vue {
this.newOffersToUserProjects = offersToUserProjectsData.data;
this.newOffersToUserProjectsHitLimit = offersToUserProjectsData.hitLimit;
// Load starred project changes if user has starred projects
if (this.starredProjectIds.length > 0) {
try {
const starredProjectChangesData = await getStarredProjectsWithChanges(
this.axios,
this.apiServer,
this.activeDid,
this.starredProjectIds,
this.lastAckedStarredProjectChangesJwtId,
);
this.newStarredProjectChanges = starredProjectChangesData.data;
this.newStarredProjectChangesHitLimit =
starredProjectChangesData.hitLimit;
} catch (error) {
logger.warn("Failed to load starred project changes:", error);
this.newStarredProjectChanges = [];
this.newStarredProjectChangesHitLimit = false;
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
logger.error("Error retrieving settings & contacts:", err);
@@ -314,5 +415,46 @@ export default class NewActivityView extends Vue {
TIMEOUTS.STANDARD,
);
}
async expandStarredProjectChangesAndMarkRead() {
this.showStarredProjectChangesDetails =
!this.showStarredProjectChangesDetails;
if (
this.showStarredProjectChangesDetails &&
this.newStarredProjectChanges.length > 0
) {
await this.$updateSettings({
lastAckedStarredProjectChangesJwtId:
this.newStarredProjectChanges[0].plan.jwtId,
});
this.notify.info(
"The starred project changes are now marked as viewed. Click in the list to keep them as new.",
TIMEOUTS.LONG,
);
}
}
async markStarredProjectChangesAsReadStartingWith(jwtId: string) {
const index = this.newStarredProjectChanges.findIndex(
(change) => change.plan.jwtId === jwtId,
);
if (index !== -1 && index < this.newStarredProjectChanges.length - 1) {
// Set to the next change's jwtId
await this.$updateSettings({
lastAckedStarredProjectChangesJwtId:
this.newStarredProjectChanges[index + 1].plan.jwtId,
});
} else {
// it's the last entry (or not found), so just keep it the same
await this.$updateSettings({
lastAckedStarredProjectChangesJwtId:
this.lastAckedStarredProjectChangesJwtId,
});
}
this.notify.info(
"All starred project changes above that line are marked as unread.",
TIMEOUTS.STANDARD,
);
}
}
</script>