import { Component, Prop, Vue, Emit } from "vue-facing-decorator";
-import { GiveRecordWithContactInfo } from "@/interfaces/give";
+import VueMarkdown from "vue-markdown-render";
+
+import { logger } from "../utils/logger";
+import {
+ createAndSubmitClaim,
+ getHeaders,
+ isHiddenDid,
+} from "../libs/endorserServer";
import EntityIcon from "./EntityIcon.vue";
-import { isHiddenDid } from "../libs/endorserServer";
import ProjectIcon from "./ProjectIcon.vue";
-import { createNotifyHelpers, NotifyFunction } from "@/utils/notify";
+import { createNotifyHelpers, NotifyFunction, TIMEOUTS } from "@/utils/notify";
import {
NOTIFY_PERSON_HIDDEN,
NOTIFY_UNKNOWN_PERSON,
} from "@/constants/notifications";
-import { TIMEOUTS } from "@/utils/notify";
-import VueMarkdown from "vue-markdown-render";
+import { GenericVerifiableCredential } from "@/interfaces";
+import { GiveRecordWithContactInfo } from "@/interfaces/give";
@Component({
components: {
@@ -274,15 +334,23 @@ import VueMarkdown from "vue-markdown-render";
},
})
export default class ActivityListItem extends Vue {
+ readonly QUICK_EMOJIS = ["👍", "👏", "❤️", "🎉", "😊", "😆", "🔥"];
+
@Prop() record!: GiveRecordWithContactInfo;
@Prop() lastViewedClaimId?: string;
@Prop() isRegistered!: boolean;
@Prop() activeDid!: string;
+ @Prop() apiServer!: string;
isHiddenDid = isHiddenDid;
notify!: ReturnType;
$notify!: NotifyFunction;
+ // Emoji-related data
+ showEmojiPicker = false;
+
+ userEmojis: string[] | null = null; // load this only when needed
+
created() {
this.notify = createNotifyHelpers(this.$notify);
}
@@ -346,5 +414,119 @@ export default class ActivityListItem extends Vue {
day: "numeric",
});
}
+
+ // Emoji-related computed properties and methods
+ get hasEmojis(): boolean {
+ return Object.keys(this.record.emojiCount).length > 0;
+ }
+
+ async loadUserEmojis(): Promise {
+ try {
+ const response = await this.axios.get(
+ `${this.apiServer}/api/v2/emoji/userEmojis?parentHandleId=${this.record.jwtId}`,
+ { headers: await getHeaders(this.activeDid) },
+ );
+ this.userEmojis = response.data;
+ } catch (error) {
+ logger.error(
+ "Error loading all emojis for parent handle id:",
+ this.record.jwtId,
+ error,
+ );
+ }
+ }
+
+ async getUserEmojis(): Promise {
+ if (!this.userEmojis) {
+ await this.loadUserEmojis();
+ }
+ return this.userEmojis || [];
+ }
+
+ selectEmoji(emoji: string) {
+ this.showEmojiPicker = false;
+ this.submitEmoji(emoji);
+ }
+
+ isUserEmoji(emoji: string): boolean {
+ return this.userEmojis?.includes(emoji) || false;
+ }
+
+ toggleEmoji(emoji: string) {
+ if (this.isUserEmoji(emoji)) {
+ this.removeEmoji(emoji);
+ } else {
+ this.submitEmoji(emoji);
+ }
+ }
+
+ async submitEmoji(emoji: string) {
+ try {
+ // Temporarily add to user emojis for UI feedback
+ if (!this.isUserEmoji(emoji)) {
+ this.record.emojiCount[emoji] = 0;
+ }
+ // Create an Emoji claim and send to the server
+ const emojiClaim: GenericVerifiableCredential = {
+ "@type": "Emoji",
+ text: emoji,
+ parentItem: { lastClaimId: this.record.jwtId },
+ };
+ const claim = await createAndSubmitClaim(
+ emojiClaim,
+ this.record.issuerDid,
+ this.apiServer,
+ this.axios,
+ );
+ if (
+ claim.success &&
+ !(claim.success as { embeddedRecordError?: string }).embeddedRecordError
+ ) {
+ this.record.emojiCount[emoji] =
+ (this.record.emojiCount[emoji] || 0) + 1;
+ this.userEmojis = [...(this.userEmojis || []), emoji];
+ } else {
+ this.notify.error("Failed to add emoji.", TIMEOUTS.STANDARD);
+ }
+ } catch (error) {
+ logger.error("Error submitting emoji:", error);
+ this.notify.error("Got error adding emoji.", TIMEOUTS.STANDARD);
+ }
+ }
+
+ async removeEmoji(emoji: string) {
+ try {
+ // Create an Emoji claim and send to the server
+ const emojiClaim: GenericVerifiableCredential = {
+ "@type": "Emoji",
+ text: emoji,
+ parentItem: { lastClaimId: this.record.jwtId },
+ };
+ const claim = await createAndSubmitClaim(
+ emojiClaim,
+ this.record.issuerDid,
+ this.apiServer,
+ this.axios,
+ );
+ if (claim.success) {
+ this.record.emojiCount[emoji] =
+ (this.record.emojiCount[emoji] || 0) - 1;
+
+ // Update local emoji count for immediate UI feedback
+ const newCount = Math.max(0, this.record.emojiCount[emoji]);
+ if (newCount === 0) {
+ delete this.record.emojiCount[emoji];
+ } else {
+ this.record.emojiCount[emoji] = newCount;
+ }
+ this.userEmojis = this.userEmojis?.filter(e => e !== emoji) || [];
+ } else {
+ this.notify.error("Failed to remove emoji.", TIMEOUTS.STANDARD);
+ }
+ } catch (error) {
+ logger.error("Error removing emoji:", error);
+ this.notify.error("Got error removing emoji.", TIMEOUTS.STANDARD);
+ }
+ }
}
diff --git a/src/interfaces/common.ts b/src/interfaces/common.ts
index b2e68d1f..0dfe37d5 100644
--- a/src/interfaces/common.ts
+++ b/src/interfaces/common.ts
@@ -80,7 +80,7 @@ export interface UserInfo {
}
export interface CreateAndSubmitClaimResult {
- success: boolean;
+ success: boolean | { embeddedRecordError?: string; claimId?: string };
error?: string;
handleId?: string;
}
diff --git a/src/interfaces/records.ts b/src/interfaces/records.ts
index ca82624c..24089b35 100644
--- a/src/interfaces/records.ts
+++ b/src/interfaces/records.ts
@@ -9,6 +9,7 @@ export interface GiveSummaryRecord {
amount: number;
amountConfirmed: number;
description: string;
+ emojiCount: Record; // Map of emoji character to count
fullClaim: GiveActionClaim;
fulfillsHandleId: string;
fulfillsPlanHandleId?: string;
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index 3e73cda4..75c9bb67 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -245,6 +245,7 @@ Raymer * @version 1.0.0 */
:last-viewed-claim-id="feedLastViewedClaimId"
:is-registered="isRegistered"
:active-did="activeDid"
+ :api-server="apiServer"
@load-claim="onClickLoadClaim"
@view-image="openImageViewer"
/>
@@ -1234,6 +1235,7 @@ export default class HomeView extends Vue {
const recipientDid = this.extractRecipientDid(claim);
const fulfillsPlan = await this.getFulfillsPlan(record);
+ const emojiCount = await record.emojiCount;
// Log record details for debugging
logger.debug("[HomeView] 🔍 Processing record:", {
@@ -1264,6 +1266,7 @@ export default class HomeView extends Vue {
provider,
fulfillsPlan,
providedByPlan,
+ emojiCount,
);
}
@@ -1487,12 +1490,14 @@ export default class HomeView extends Vue {
provider: Provider | undefined,
fulfillsPlan?: FulfillsPlan,
providedByPlan?: ProvidedByPlan,
+ emojiCount?: Record,
): GiveRecordWithContactInfo {
return {
...record,
jwtId: record.jwtId,
fullClaim: record.fullClaim,
description: record.description || "",
+ emojiCount: emojiCount || {},
handleId: record.handleId,
issuerDid: record.issuerDid,
fulfillsPlanHandleId: record.fulfillsPlanHandleId,