forked from jsnbuchanan/crowd-funder-for-time-pwa
- Add NOTIFY_CONTACT_LOADING_ISSUE, NOTIFY_FEED_LOADING_ISSUE, and NOTIFY_CONFIRMATION_ERROR constants to notifications.ts - Update HomeView.vue to import and use notification constants instead of literal strings - Update migration templates to document constants vs literal strings pattern - Add comprehensive documentation for notification constants usage Ensures consistency with established pattern used in ActivityListItem.vue and other migrated components. Linter passes without errors.
366 lines
12 KiB
Vue
366 lines
12 KiB
Vue
<template>
|
|
<li>
|
|
<!-- Last viewed separator -->
|
|
<div
|
|
v-if="record.jwtId == lastViewedClaimId"
|
|
class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-6 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
|
|
</span>
|
|
</div>
|
|
|
|
<div
|
|
class="flex items-center justify-between gap-2 text-lg bg-slate-200 border border-slate-300 border-b-0 rounded-t-md px-3 sm:px-4 py-1 sm:py-2"
|
|
>
|
|
<div class="flex items-center gap-2">
|
|
<router-link
|
|
v-if="record.issuerDid && !isHiddenDid(record.issuerDid)"
|
|
:to="{
|
|
path: '/did/' + encodeURIComponent(record.issuerDid),
|
|
}"
|
|
title="More details about this person"
|
|
>
|
|
<EntityIcon
|
|
:entity-id="record.issuerDid"
|
|
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
/>
|
|
</router-link>
|
|
<font-awesome
|
|
v-else-if="isHiddenDid(record.issuerDid)"
|
|
icon="eye-slash"
|
|
class="text-slate-400 !size-[2rem] cursor-pointer"
|
|
@click="notifyHiddenPerson"
|
|
/>
|
|
<font-awesome
|
|
v-else
|
|
icon="person-circle-question"
|
|
class="text-slate-400 !size-[2rem] cursor-pointer"
|
|
@click="notifyUnknownPerson"
|
|
/>
|
|
|
|
<div>
|
|
<h3 v-if="record.issuer.known" class="font-semibold leading-tight">
|
|
{{ record.issuer.displayName }}
|
|
</h3>
|
|
<p class="ms-auto text-xs text-slate-500 italic">
|
|
{{ friendlyDate }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<a
|
|
class="cursor-pointer"
|
|
data-testid="circle-info-link"
|
|
@click="$emit('loadClaim', record.jwtId)"
|
|
>
|
|
<font-awesome icon="circle-info" class="fa-fw text-slate-500" />
|
|
</a>
|
|
</div>
|
|
|
|
<div class="bg-slate-100 rounded-b-md border border-slate-300 p-3 sm:p-4">
|
|
<!-- Record Image -->
|
|
<div
|
|
v-if="record.image"
|
|
class="bg-cover mb-2 -mt-3 sm:-mt-4 -mx-3 sm:-mx-4"
|
|
:style="`background-image: url(${transformImageUrlForCors(record.image)});`"
|
|
>
|
|
<a
|
|
class="block bg-slate-100/50 backdrop-blur-md px-6 py-4 cursor-pointer"
|
|
@click="$emit('viewImage', record.image)"
|
|
>
|
|
<img
|
|
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md"
|
|
:src="transformImageUrlForCors(record.image)"
|
|
alt="Activity image"
|
|
@load="cacheImage(record.image)"
|
|
/>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<p class="font-medium">
|
|
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)">
|
|
{{ description }}
|
|
</a>
|
|
</p>
|
|
|
|
<div
|
|
class="relative flex justify-between gap-4 max-w-[40rem] mx-auto mt-4"
|
|
>
|
|
<!-- Source -->
|
|
<div
|
|
class="w-[7rem] sm:w-[12rem] text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
|
>
|
|
<div class="relative w-fit mx-auto">
|
|
<div>
|
|
<!-- Project Icon -->
|
|
<div v-if="record.providerPlanName">
|
|
<router-link
|
|
:to="{
|
|
path:
|
|
'/project/' +
|
|
encodeURIComponent(record.providerPlanHandleId || ''),
|
|
}"
|
|
title="View project details"
|
|
>
|
|
<ProjectIcon
|
|
:entity-id="record.providerPlanHandleId || ''"
|
|
:icon-size="48"
|
|
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
|
/>
|
|
</router-link>
|
|
</div>
|
|
<!-- Identicon for DIDs -->
|
|
<div v-else-if="record.agentDid">
|
|
<router-link
|
|
v-if="!isHiddenDid(record.agentDid)"
|
|
:to="{
|
|
path: '/did/' + encodeURIComponent(record.agentDid),
|
|
}"
|
|
title="More details about this person"
|
|
>
|
|
<EntityIcon
|
|
:entity-id="record.agentDid"
|
|
:profile-image-url="record.issuer.profileImageUrl"
|
|
class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
|
|
/>
|
|
</router-link>
|
|
<font-awesome
|
|
v-else
|
|
icon="eye-slash"
|
|
class="text-slate-300 !size-[3rem] sm:!size-[4rem]"
|
|
@click="notifyHiddenPerson"
|
|
/>
|
|
</div>
|
|
<!-- Unknown Person -->
|
|
<div v-else>
|
|
<font-awesome
|
|
icon="person-circle-question"
|
|
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
|
@click="notifyUnknownPerson"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="record.providerPlanName || record.giver.known"
|
|
class="text-xs mt-2 truncate"
|
|
>
|
|
<font-awesome
|
|
:icon="record.providerPlanName ? 'users' : 'user'"
|
|
class="fa-fw text-slate-400"
|
|
/>
|
|
{{ record.providerPlanName || record.giver.displayName }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Arrow -->
|
|
<div
|
|
class="absolute inset-x-[7rem] sm:inset-x-[12rem] mx-2 top-1/2 -translate-y-1/2"
|
|
>
|
|
<div
|
|
class="text-sm text-center leading-none font-semibold pe-2 sm:pe-4"
|
|
>
|
|
{{ fetchAmount }}
|
|
</div>
|
|
|
|
<div class="flex items-center">
|
|
<hr
|
|
class="grow border-t-[18px] sm:border-t-[24px] border-slate-300"
|
|
/>
|
|
|
|
<div
|
|
class="shrink-0 w-0 h-0 border border-slate-300 border-t-[20px] sm:border-t-[25px] border-t-transparent border-b-[20px] sm:border-b-[25px] border-b-transparent border-s-[27px] sm:border-s-[34px] border-e-0"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Destination -->
|
|
<div
|
|
class="w-[7rem] sm:w-[12rem] text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
|
>
|
|
<div class="relative w-fit mx-auto">
|
|
<div>
|
|
<!-- Project Icon -->
|
|
<div v-if="record.recipientProjectName">
|
|
<router-link
|
|
:to="{
|
|
path:
|
|
'/project/' +
|
|
encodeURIComponent(record.fulfillsPlanHandleId || ''),
|
|
}"
|
|
title="View project details"
|
|
>
|
|
<ProjectIcon
|
|
:entity-id="record.fulfillsPlanHandleId || ''"
|
|
:icon-size="48"
|
|
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
|
/>
|
|
</router-link>
|
|
</div>
|
|
<!-- Identicon for DIDs -->
|
|
<div v-else-if="record.recipientDid">
|
|
<router-link
|
|
v-if="!isHiddenDid(record.recipientDid)"
|
|
:to="{
|
|
path: '/did/' + encodeURIComponent(record.recipientDid),
|
|
}"
|
|
title="More details about this person"
|
|
>
|
|
<EntityIcon
|
|
:entity-id="record.recipientDid"
|
|
:profile-image-url="record.receiver.profileImageUrl"
|
|
class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
|
|
/>
|
|
</router-link>
|
|
<font-awesome
|
|
v-else
|
|
icon="eye-slash"
|
|
class="text-slate-300 !size-[3rem] sm:!size-[4rem]"
|
|
@click="notifyHiddenPerson"
|
|
/>
|
|
</div>
|
|
<!-- Unknown Person -->
|
|
<div v-else>
|
|
<font-awesome
|
|
icon="person-circle-question"
|
|
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
|
@click="notifyUnknownPerson"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="record.recipientProjectName || record.receiver.known"
|
|
class="text-xs mt-2 truncate"
|
|
>
|
|
<font-awesome
|
|
:icon="record.recipientProjectName ? 'users' : 'user'"
|
|
class="fa-fw text-slate-400"
|
|
/>
|
|
{{ record.recipientProjectName || record.receiver.displayName }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Prop, Vue, Emit } from "vue-facing-decorator";
|
|
import { GiveRecordWithContactInfo } from "@/interfaces/give";
|
|
import EntityIcon from "./EntityIcon.vue";
|
|
import {
|
|
isGiveClaimType,
|
|
notifyWhyCannotConfirm,
|
|
transformImageUrlForCors,
|
|
} from "../libs/util";
|
|
import { containsHiddenDid, isHiddenDid } from "../libs/endorserServer";
|
|
import ProjectIcon from "./ProjectIcon.vue";
|
|
import { createNotifyHelpers } from "@/utils/notify";
|
|
import {
|
|
NOTIFY_PERSON_HIDDEN,
|
|
NOTIFY_UNKNOWN_PERSON,
|
|
} from "@/constants/notifications";
|
|
import { TIMEOUTS } from "@/utils/notify";
|
|
|
|
@Component({
|
|
components: {
|
|
EntityIcon,
|
|
ProjectIcon,
|
|
},
|
|
})
|
|
export default class ActivityListItem extends Vue {
|
|
@Prop() record!: GiveRecordWithContactInfo;
|
|
@Prop() lastViewedClaimId?: string;
|
|
@Prop() isRegistered!: boolean;
|
|
@Prop() activeDid!: string;
|
|
@Prop() confirmerIdList?: string[];
|
|
|
|
isHiddenDid = isHiddenDid;
|
|
notify!: ReturnType<typeof createNotifyHelpers>;
|
|
$notify!: (notification: any, timeout?: number) => void;
|
|
|
|
created() {
|
|
this.notify = createNotifyHelpers(this.$notify);
|
|
}
|
|
|
|
notifyHiddenPerson() {
|
|
this.notify.warning(NOTIFY_PERSON_HIDDEN.message, TIMEOUTS.STANDARD);
|
|
}
|
|
|
|
notifyUnknownPerson() {
|
|
this.notify.warning(NOTIFY_UNKNOWN_PERSON.message, TIMEOUTS.STANDARD);
|
|
}
|
|
|
|
@Emit()
|
|
cacheImage(image: string) {
|
|
return image;
|
|
}
|
|
|
|
get fetchAmount(): string {
|
|
const claim =
|
|
(this.record.fullClaim as any)?.claim || this.record.fullClaim;
|
|
|
|
const amount = claim.object?.amountOfThisGood
|
|
? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
|
|
: "";
|
|
|
|
return amount;
|
|
}
|
|
|
|
get description(): string {
|
|
const claim =
|
|
(this.record.fullClaim as any)?.claim || this.record.fullClaim;
|
|
|
|
return `${claim?.description || ""}`;
|
|
}
|
|
|
|
private displayAmount(code: string, amt: number) {
|
|
return `${amt} ${this.currencyShortWordForCode(code, amt === 1)}`;
|
|
}
|
|
|
|
private currencyShortWordForCode(unitCode: string, single: boolean) {
|
|
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
|
}
|
|
|
|
get canConfirm(): boolean {
|
|
if (!this.isRegistered) return false;
|
|
if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false;
|
|
if (this.confirmerIdList?.includes(this.activeDid)) return false;
|
|
if (this.record.issuerDid === this.activeDid) return false;
|
|
if (containsHiddenDid(this.record.fullClaim)) return false;
|
|
return true;
|
|
}
|
|
|
|
handleConfirmClick() {
|
|
if (!this.canConfirm) {
|
|
notifyWhyCannotConfirm(
|
|
(msg, timeout) => this.notify.info(msg.text ?? "", timeout),
|
|
this.isRegistered,
|
|
this.record.fullClaim?.["@type"],
|
|
this.record,
|
|
this.activeDid,
|
|
this.confirmerIdList,
|
|
);
|
|
return;
|
|
}
|
|
this.$emit("confirmClaim", this.record);
|
|
}
|
|
|
|
get friendlyDate(): string {
|
|
const date = new Date(this.record.issuedAt);
|
|
return date.toLocaleDateString(undefined, {
|
|
year: "numeric",
|
|
month: "short",
|
|
day: "numeric",
|
|
});
|
|
}
|
|
|
|
transformImageUrlForCors(imageUrl: string): string {
|
|
return transformImageUrlForCors(imageUrl);
|
|
}
|
|
}
|
|
</script>
|