From 9904e082aa048f15c5992fce7834af1ca617c16c Mon Sep 17 00:00:00 2001
From: Trent Larson <trent@trentlarson.com>
Date: Wed, 18 Dec 2024 16:05:43 -0700
Subject: [PATCH] refine claim certificate view

---
 src/libs/endorserServer.ts         | 10 ++++
 src/views/ClaimCertificateView.vue | 75 +++++++++++++++++++++++-------
 src/views/ClaimView.vue            | 48 ++++++++++++-------
 3 files changed, 98 insertions(+), 35 deletions(-)

diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts
index 54ff1d2f2..ff4c6f341 100644
--- a/src/libs/endorserServer.ts
+++ b/src/libs/endorserServer.ts
@@ -476,6 +476,16 @@ export function didInfo(
   return didInfoForContact(did, activeDid, contact, allMyDids).displayName;
 }
 
+/**
+ * return text description without any references to "you" as user
+ */
+export function didInfoForCertificate(
+  did: string | undefined,
+  contacts: Contact[],
+): string {
+  return didInfoForContact(did, undefined, contactForDid(did, contacts), []).displayName;
+}
+
 let passkeyAccessToken: string = "";
 let passkeyTokenExpirationEpochSeconds: number = 0;
 
diff --git a/src/views/ClaimCertificateView.vue b/src/views/ClaimCertificateView.vue
index c87c39eac..a39567ae0 100644
--- a/src/views/ClaimCertificateView.vue
+++ b/src/views/ClaimCertificateView.vue
@@ -21,8 +21,8 @@ import { Component, Vue } from "vue-facing-decorator";
 import { nextTick } from "vue";
 import QRCode from "qrcode";
 
-import { NotificationIface } from "@/constants/app";
-import { retrieveSettingsForActiveAccount } from "@/db/index";
+import { APP_SERVER, NotificationIface } from "@/constants/app";
+import { db, retrieveSettingsForActiveAccount } from "@/db/index";
 import * as endorserServer from "@/libs/endorserServer";
 
 @Component
@@ -56,7 +56,9 @@ export default class ClaimViewCertificate extends Vue {
       if (response.ok) {
         this.claimData = await response.json();
         await nextTick(); // Wait for the DOM to update
-        this.drawCanvas();
+        if (this.claimData) {
+          this.drawCanvas(this.claimData);
+        }
       } else {
         throw new Error(`Error fetching claim: ${response.statusText}`);
       }
@@ -71,7 +73,12 @@ export default class ClaimViewCertificate extends Vue {
     }
   }
 
-  drawCanvas() {
+  async drawCanvas(
+    claimData: endorserServer.GenericCredWrapper<endorserServer.GenericVerifiableCredential>,
+  ) {
+    await db.open();
+    const allContacts = await db.contacts.toArray();
+
     const canvas = this.$refs.claimCanvas as HTMLCanvasElement;
     if (canvas) {
       const CANVAS_WIDTH = 1100;
@@ -96,21 +103,43 @@ export default class ClaimViewCertificate extends Vue {
           ctx.font = "bold 20px Arial";
           const claimTypeText =
             this.endorserServer.capitalizeAndInsertSpacesBeforeCaps(
-              this.claimData.claimType,
+              claimData.claimType || "",
             );
           const claimTypeWidth = ctx.measureText(claimTypeText).width;
           ctx.fillText(
             claimTypeText,
             (CANVAS_WIDTH - claimTypeWidth) / 2, // Center horizontally
-            CANVAS_HEIGHT * 0.35,
+            CANVAS_HEIGHT * 0.33,
           );
 
+          if (claimData.claim.agent) {
+            const presentedText = "Presented to ";
+            ctx.font = "14px Arial";
+            const presentedWidth = ctx.measureText(presentedText).width;
+            ctx.fillText(
+              presentedText,
+              (CANVAS_WIDTH - presentedWidth) / 2, // Center horizontally
+              CANVAS_HEIGHT * 0.37,
+            );
+            const agentText = endorserServer.didInfoForCertificate(
+              claimData.claim.agent,
+              allContacts,
+            );
+            ctx.font = "bold 20px Arial";
+            const agentWidth = ctx.measureText(agentText).width;
+            ctx.fillText(
+              agentText,
+              (CANVAS_WIDTH - agentWidth) / 2, // Center horizontally
+              CANVAS_HEIGHT * 0.4,
+            );
+          }
+
           const descriptionText =
-            this.claimData.claim.description || this.claimData.claim.name;
+            claimData.claim.name || claimData.claim.description;
           if (descriptionText) {
             const descriptionLine =
               descriptionText.length > 50
-                ? descriptionText.substring(0, 47) + "..."
+                ? descriptionText.substring(0, 85) + "..."
                 : descriptionText;
             ctx.font = "14px Arial";
             const descriptionWidth = ctx.measureText(descriptionLine).width;
@@ -121,26 +150,38 @@ export default class ClaimViewCertificate extends Vue {
             );
           }
 
+          // Draw claim issuer & recipient
+          if (claimData.issuer) {
+            ctx.font = "14px Arial";
+            const issuerText =
+              "Issued by " +
+              endorserServer.didInfoForCertificate(
+                claimData.issuer,
+                allContacts,
+              );
+            ctx.fillText(issuerText, CANVAS_WIDTH * 0.3, CANVAS_HEIGHT * 0.6);
+          }
+
           // Draw claim ID
           ctx.font = "14px Arial";
-          ctx.fillText(this.claimId, CANVAS_WIDTH * 0.3, CANVAS_HEIGHT * 0.62);
+          ctx.fillText(this.claimId, CANVAS_WIDTH * 0.3, CANVAS_HEIGHT * 0.7);
           ctx.fillText(
             "via EndorserSearch.com",
             CANVAS_WIDTH * 0.3,
-            CANVAS_HEIGHT * 0.65,
+            CANVAS_HEIGHT * 0.73,
           );
 
           // Generate and draw QR code
           const qrCodeCanvas = document.createElement("canvas");
-          await QRCode.toCanvas(qrCodeCanvas, window.location.href, {
-            width: 150,
-            color: { light: "#0000" /* Transparent background */ },
-          });
-          ctx.drawImage(
+          await QRCode.toCanvas(
             qrCodeCanvas,
-            CANVAS_WIDTH * 0.57,
-            CANVAS_HEIGHT * 0.55,
+            APP_SERVER + "/claim/" + this.claimId,
+            {
+              width: 150,
+              color: { light: "#0000" /* Transparent background */ },
+            },
           );
+          ctx.drawImage(qrCodeCanvas, CANVAS_WIDTH * 0.6, CANVAS_HEIGHT * 0.55);
         };
       }
     }
diff --git a/src/views/ClaimView.vue b/src/views/ClaimView.vue
index 1e593e32a..c8f2725d9 100644
--- a/src/views/ClaimView.vue
+++ b/src/views/ClaimView.vue
@@ -20,24 +20,36 @@
     <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
       <div class="block flex gap-4 overflow-hidden">
         <div class="overflow-hidden">
-          <h2 class="text-md font-bold">
-            {{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }}
-            <button
-              v-if="
-                ['GiveAction', 'Offer', 'PlanAction'].includes(
-                  veriClaim.claimType as string,
-                ) && veriClaim.issuer === activeDid
-                // a PlanAction agent also could edit one of those,
-                // but rather than add more Plan-specific logic to detect the agent
-                // we'll let them click the Project link and edit from there
-              "
-              @click="onClickEditClaim"
-              title="Edit"
-              data-testId="editClaimButton"
-            >
-              <fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
-            </button>
-          </h2>
+          <div class="flex justify-between">
+            <h2 class="text-md font-bold">
+              {{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }}
+              <button
+                v-if="
+                  ['GiveAction', 'Offer', 'PlanAction'].includes(
+                    veriClaim.claimType as string,
+                  ) && veriClaim.issuer === activeDid
+                  // a PlanAction agent also could edit one of those,
+                  // but rather than add more Plan-specific logic to detect the agent
+                  // we'll let them click the Project link and edit from there
+                "
+                @click="onClickEditClaim"
+                title="Edit"
+                data-testId="editClaimButton"
+              >
+                <fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
+              </button>
+            </h2>
+            <!--
+            <div>
+              <router-link
+                :to="'/claim-cert/' + encodeURIComponent(veriClaim.id)"
+                class="text-blue-500 mt-2"
+              >
+                <fa icon="square" class="text-white bg-yellow-500 p-1" />
+              </router-link>
+            </div>
+            -->
+          </div>
           <div class="text-sm">
             <div data-testId="description">
               <fa icon="message" class="fa-fw text-slate-400" />