From a2e19d7e9a6a8f2843df1aaa803157eee3a035fb Mon Sep 17 00:00:00 2001 From: Matthew Raymer <mraymer@osinetwork.net> Date: Tue, 25 Feb 2025 11:36:24 +0000 Subject: [PATCH] fix: improve TypeScript type safety across views Changes: - Add proper type annotations for component properties - Fix null checks with optional chaining - Add missing interface properties - Replace any with proper types where possible - Move interfaces from endorserServer to interfaces/ - Add proper Router and Route typing - Add default empty string for optional text fields This improves type safety and reduces TypeScript errors across views. --- src/interfaces/claims.ts | 7 +++- src/interfaces/records.ts | 1 + src/views/ClaimAddRawView.vue | 8 ++-- src/views/ClaimCertificateView.vue | 4 +- src/views/ClaimView.vue | 23 ++++++------ src/views/ConfirmGiftView.vue | 17 +++++---- src/views/GiftedDetailsView.vue | 59 ++++++++++++++---------------- src/views/OfferDetailsView.vue | 37 +++++++++---------- src/views/ProjectViewView.vue | 20 +++++----- 9 files changed, 92 insertions(+), 84 deletions(-) diff --git a/src/interfaces/claims.ts b/src/interfaces/claims.ts index 28322a30..2b14c734 100644 --- a/src/interfaces/claims.ts +++ b/src/interfaces/claims.ts @@ -30,7 +30,12 @@ export interface OfferVerifiableCredential extends GenericVerifiableCredential { includesObject?: { amountOfThisGood: number; unitCode: string }; itemOffered?: { description?: string; - isPartOf?: { identifier?: string; lastClaimId?: string; "@type"?: string }; + isPartOf?: { + identifier?: string; + lastClaimId?: string; + "@type"?: string; + name?: string; + }; }; offeredBy?: { identifier: string }; recipient?: { identifier: string }; diff --git a/src/interfaces/records.ts b/src/interfaces/records.ts index 63422f8e..0e253be9 100644 --- a/src/interfaces/records.ts +++ b/src/interfaces/records.ts @@ -2,6 +2,7 @@ import { GiveVerifiableCredential, OfferVerifiableCredential } from "./claims"; // a summary record; the VC is found the fullClaim field export interface GiveSummaryRecord { + type?: string; agentDid: string; amount: number; amountConfirmed: number; diff --git a/src/views/ClaimAddRawView.vue b/src/views/ClaimAddRawView.vue index a33caab9..f7d36ac9 100644 --- a/src/views/ClaimAddRawView.vue +++ b/src/views/ClaimAddRawView.vue @@ -30,7 +30,6 @@ <script lang="ts"> import { Component, Vue } from "vue-facing-decorator"; -import { Router } from "vue-router"; import QuickNav from "../components/QuickNav.vue"; import { NotificationIface } from "../constants/app"; @@ -38,12 +37,15 @@ import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "../db/index"; import * as serverUtil from "../libs/endorserServer"; import * as libsUtil from "../libs/util"; import { errorStringForLog } from "../libs/endorserServer"; +import { Router, RouteLocationNormalizedLoaded } from "vue-router"; @Component({ components: { QuickNav }, }) export default class ClaimAddRawView extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; + $route!: RouteLocationNormalizedLoaded; + $router!: Router; accountIdentityStr: string = "null"; activeDid = ""; @@ -55,7 +57,7 @@ export default class ClaimAddRawView extends Vue { this.activeDid = settings.activeDid || ""; this.apiServer = settings.apiServer || ""; - this.claimStr = (this.$route as Router).query["claim"]; + this.claimStr = (this.$route.query["claim"] as string) || ""; if (this.claimStr) { try { const veriClaim = JSON.parse(this.claimStr); @@ -65,7 +67,7 @@ export default class ClaimAddRawView extends Vue { } } else { // there may be no link that uses this, meaning you'd have to enter it in a browser - const claimJwtId = (this.$route as Router).query["claimJwtId"]; + const claimJwtId = (this.$route.query["claimJwtId"] as string) || ""; if (claimJwtId) { const urlPath = libsUtil.isGlobalUri(claimJwtId) ? "/api/claim/byHandle/" diff --git a/src/views/ClaimCertificateView.vue b/src/views/ClaimCertificateView.vue index 53de8f76..d0204998 100644 --- a/src/views/ClaimCertificateView.vue +++ b/src/views/ClaimCertificateView.vue @@ -14,7 +14,7 @@ import { Component, Vue } from "vue-facing-decorator"; import { nextTick } from "vue"; import QRCode from "qrcode"; - +import { GenericVerifiableCredential } from "../interfaces"; import { APP_SERVER, NotificationIface } from "../constants/app"; import { db, retrieveSettingsForActiveAccount } from "../db/index"; import * as serverUtil from "../libs/endorserServer"; @@ -81,7 +81,7 @@ export default class ClaimCertificateView extends Vue { } async drawCanvas( - claimData: serverUtil.GenericCredWrapper<serverUtil.GenericVerifiableCredential>, + claimData: serverUtil.GenericCredWrapper<GenericVerifiableCredential>, confirmerIds: Array<string>, ) { await db.open(); diff --git a/src/views/ClaimView.vue b/src/views/ClaimView.vue index ad51b09d..95259a0b 100644 --- a/src/views/ClaimView.vue +++ b/src/views/ClaimView.vue @@ -22,7 +22,7 @@ <div class="w-full"> <div class="flex columns-3"> <h2 class="text-md font-bold w-full"> - {{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }} + {{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType || '') }} <button v-if=" ['GiveAction', 'Offer', 'PlanAction'].includes( @@ -64,8 +64,9 @@ <div data-testId="description"> <font-awesome icon="message" class="fa-fw text-slate-400" /> {{ - veriClaim.claim?.itemOffered?.description || - veriClaim.claim?.description + (veriClaim.claim?.itemOffered as any)?.description || + (veriClaim.claim as any)?.description || + '' }} </div> <div> @@ -77,9 +78,9 @@ Recorded {{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }} </div> - <div v-if="veriClaim.claim.image" class="flex justify-center"> - <a :href="veriClaim.claim.image" target="_blank"> - <img :src="veriClaim.claim.image" class="h-24 rounded-xl" /> + <div v-if="(veriClaim.claim as any).image" class="flex justify-center"> + <a :href="(veriClaim.claim as any).image" target="_blank"> + <img :src="(veriClaim.claim as any).image" class="h-24 rounded-xl" /> </a> </div> @@ -507,7 +508,7 @@ import * as R from "ramda"; import { Component, Vue } from "vue-facing-decorator"; import { Router, RouteLocationNormalizedLoaded } from "vue-router"; import { useClipboard } from "@vueuse/core"; - +import { GenericVerifiableCredential } from "../interfaces"; import GiftedDialog from "../components/GiftedDialog.vue"; import QuickNav from "../components/QuickNav.vue"; import { NotificationIface } from "../constants/app"; @@ -542,8 +543,8 @@ export default class ClaimView extends Vue { confirmerIdList: string[] = []; // list of DIDs that have confirmed this claim excluding the issuer confsVisibleErrorMessage = ""; confsVisibleToIdList: string[] = []; // list of DIDs that can see any confirmer - detailsForGive = null; - detailsForOffer = null; + detailsForGive: { fulfillsPlanHandleId?: string; fulfillsType?: string; fulfillsHandleId?: string } | null = null; + detailsForOffer: { fulfillsPlanHandleId?: string } | null = null; fullClaim = null; fullClaimDump = ""; fullClaimMessage = ""; @@ -556,7 +557,7 @@ export default class ClaimView extends Vue { showVeriClaimDump = false; veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; veriClaimDump = ""; - veriClaimDidsVisible = {}; + veriClaimDidsVisible: { [key: string]: string[] } = {}; windowLocation = window.location.href; R = R; @@ -877,7 +878,7 @@ export default class ClaimView extends Vue { ), ), ); - const confirmationClaim: serverUtil.GenericVerifiableCredential = { + const confirmationClaim: GenericVerifiableCredential = { "@context": "https://schema.org", "@type": "AgreeAction", object: goodClaim, diff --git a/src/views/ConfirmGiftView.vue b/src/views/ConfirmGiftView.vue index 0e8c7aa3..ea0d8c0c 100644 --- a/src/views/ConfirmGiftView.vue +++ b/src/views/ConfirmGiftView.vue @@ -408,24 +408,26 @@ import * as yaml from "js-yaml"; import * as R from "ramda"; import { Component, Vue } from "vue-facing-decorator"; import { useClipboard } from "@vueuse/core"; -import { Router } from "vue-router"; - +import { RouteLocationNormalizedLoaded, Router } from "vue-router"; +import { GenericVerifiableCredential } from "../interfaces"; import QuickNav from "../components/QuickNav.vue"; import { NotificationIface } from "../constants/app"; import { db, retrieveSettingsForActiveAccount } from "../db/index"; import { Contact } from "../db/tables/contacts"; import * as serverUtil from "../libs/endorserServer"; -import { displayAmount, GiveSummaryRecord } from "../libs/endorserServer"; +import { GiveSummaryRecord } from "../interfaces"; +import { displayAmount } from "../libs/endorserServer"; import * as libsUtil from "../libs/util"; import { isGiveAction, retrieveAccountDids } from "../libs/util"; import TopMessage from "../components/TopMessage.vue"; @Component({ - methods: { displayAmount }, components: { TopMessage, QuickNav }, }) -export default class ClaimView extends Vue { +export default class ConfirmGiftView extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; + $route!: RouteLocationNormalizedLoaded; + $router!: Router; activeDid = ""; allMyDids: Array<string> = []; @@ -447,13 +449,14 @@ export default class ClaimView extends Vue { urlForNewGive = ""; veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; veriClaimDump = ""; - veriClaimDidsVisible = {}; + veriClaimDidsVisible: { [key: string]: string[] } = {}; windowLocation = window.location.href; R = R; yaml = yaml; libsUtil = libsUtil; serverUtil = serverUtil; + displayAmount = displayAmount; resetThisValues() { this.confirmerIdList = []; @@ -719,7 +722,7 @@ export default class ClaimView extends Vue { ), ), ); - const confirmationClaim: serverUtil.GenericVerifiableCredential = { + const confirmationClaim: GenericVerifiableCredential = { "@context": "https://schema.org", "@type": "AgreeAction", object: goodClaim, diff --git a/src/views/GiftedDetailsView.vue b/src/views/GiftedDetailsView.vue index 7da3f0ee..08f1ba6f 100644 --- a/src/views/GiftedDetailsView.vue +++ b/src/views/GiftedDetailsView.vue @@ -261,21 +261,20 @@ <script lang="ts"> import { Component, Vue } from "vue-facing-decorator"; -import { Router } from "vue-router"; +import { RouteLocationNormalizedLoaded, Router } from "vue-router"; import ImageMethodDialog from "../components/ImageMethodDialog.vue"; import QuickNav from "../components/QuickNav.vue"; import TopMessage from "../components/TopMessage.vue"; import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "../constants/app"; import { db, retrieveSettingsForActiveAccount } from "../db/index"; +import { GenericCredWrapper, GiveVerifiableCredential } from "../interfaces"; import { createAndSubmitGive, didInfo, editAndSubmitGive, - GenericCredWrapper, getHeaders, getPlanFromCache, - GiveVerifiableCredential, hydrateGive, } from "../libs/endorserServer"; import * as libsUtil from "../libs/util"; @@ -290,6 +289,8 @@ import { retrieveAccountDids } from "../libs/util"; }) export default class GiftedDetails extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; + $route!: RouteLocationNormalizedLoaded; + $router!: Router; activeDid = ""; apiServer = ""; @@ -322,9 +323,9 @@ export default class GiftedDetails extends Vue { async mounted() { try { - this.prevCredToEdit = (this.$route as Router).query["prevCredToEdit"] + this.prevCredToEdit = (this.$route.query["prevCredToEdit"] as string) ? (JSON.parse( - (this.$route as Router).query["prevCredToEdit"], + (this.$route.query["prevCredToEdit"] as string), ) as GenericCredWrapper<GiveVerifiableCredential>) : undefined; } catch (error) { @@ -341,24 +342,22 @@ export default class GiftedDetails extends Vue { const prevAmount = this.prevCredToEdit?.claim?.object?.amountOfThisGood; this.amountInput = - (this.$route as Router).query["amountInput"] || + (this.$route.query["amountInput"] as string) || (prevAmount ? String(prevAmount) : "") || this.amountInput; this.description = - (this.$route as Router).query["description"] || + (this.$route.query["description"] as string) || this.prevCredToEdit?.claim?.description || this.description; - this.destinationPathAfter = (this.$route as Router).query[ - "destinationPathAfter" - ]; - this.giverDid = ((this.$route as Router).query["giverDid"] || - this.prevCredToEdit?.claim?.agent?.identifier || + this.destinationPathAfter = (this.$route.query["destinationPathAfter"] as string) || ""; + this.giverDid = ((this.$route.query["giverDid"] as string) || + (this.prevCredToEdit?.claim?.agent as any)?.identifier || this.giverDid) as string; this.giverName = - ((this.$route as Router).query["giverName"] as string) || ""; + ((this.$route.query["giverName"] as string) || ""); this.hideBackButton = - (this.$route as Router).query["hideBackButton"] === "true"; - this.message = ((this.$route as Router).query["message"] as string) || ""; + (this.$route.query["hideBackButton"] as string) === "true"; + this.message = ((this.$route.query["message"] as string) || ""); // find any offer ID const fulfills = this.prevCredToEdit?.claim?.fulfills; @@ -368,7 +367,7 @@ export default class GiftedDetails extends Vue { ? [fulfills] : []; const offer = fulfillsArray.find((rec) => rec["@type"] === "Offer"); - this.offerId = ((this.$route as Router).query["offerId"] || + this.offerId = ((this.$route.query["offerId"] as string) || offer?.identifier || this.offerId) as string; @@ -378,7 +377,7 @@ export default class GiftedDetails extends Vue { ); // eslint-disable-next-line prettier/prettier this.fulfillsProjectId = - ((this.$route as Router).query["fulfillsProjectId"] || + ((this.$route.query["fulfillsProjectId"] as string) || fulfillsProject?.identifier || this.fulfillsProjectId) as string; @@ -392,40 +391,38 @@ export default class GiftedDetails extends Vue { const providerProject = providerArray.find( (rec) => rec["@type"] === "PlanAction", ); - this.providerProjectId = ((this.$route as Router).query[ - "providerProjectId" - ] || + this.providerProjectId = ((this.$route.query["providerProjectId"] as string) || providerProject?.identifier || this.providerProjectId) as string; - this.recipientDid = ((this.$route as Router).query["recipientDid"] || + this.recipientDid = ((this.$route.query["recipientDid"] as string) || this.prevCredToEdit?.claim?.recipient?.identifier) as string; this.recipientName = - ((this.$route as Router).query["recipientName"] as string) || ""; - this.unitCode = ((this.$route as Router).query["unitCode"] || + ((this.$route.query["recipientName"] as string) || ""); + this.unitCode = ((this.$route.query["unitCode"] as string) || this.prevCredToEdit?.claim?.object?.unitCode || this.unitCode) as string; this.imageUrl = - ((this.$route as Router).query["imageUrl"] as string) || + ((this.$route.query["imageUrl"] as string) || this.prevCredToEdit?.claim?.image || localStorage.getItem("imageUrl") || - this.imageUrl; + this.imageUrl) as string; // this is an endpoint for sharing project info to highlight something given // https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target - if ((this.$route as Router).query["shareTitle"]) { + if ((this.$route.query["shareTitle"] as string)) { this.description = - ((this.$route as Router).query["shareTitle"] as string) + + ((this.$route.query["shareTitle"] as string) || "") + (this.description ? "\n" + this.description : ""); } - if ((this.$route as Router).query["shareText"]) { + if ((this.$route.query["shareText"] as string)) { this.description = (this.description ? this.description + "\n" : "") + - ((this.$route as Router).query["shareText"] as string); + ((this.$route.query["shareText"] as string) || ""); } - if ((this.$route as Router).query["shareUrl"]) { - this.imageUrl = (this.$route as Router).query["shareUrl"] as string; + if ((this.$route.query["shareUrl"] as string)) { + this.imageUrl = (this.$route.query["shareUrl"] as string); } const settings = await retrieveSettingsForActiveAccount(); diff --git a/src/views/OfferDetailsView.vue b/src/views/OfferDetailsView.vue index 21016a24..a16c1461 100644 --- a/src/views/OfferDetailsView.vue +++ b/src/views/OfferDetailsView.vue @@ -176,20 +176,19 @@ <script lang="ts"> import { Component, Vue } from "vue-facing-decorator"; -import { Router } from "vue-router"; +import { RouteLocationNormalizedLoaded, Router } from "vue-router"; import QuickNav from "../components/QuickNav.vue"; import TopMessage from "../components/TopMessage.vue"; import { NotificationIface } from "../constants/app"; import { db, retrieveSettingsForActiveAccount } from "../db/index"; +import { GenericCredWrapper, OfferVerifiableCredential } from "../interfaces"; import { createAndSubmitOffer, didInfo, editAndSubmitOffer, - GenericCredWrapper, getPlanFromCache, hydrateOffer, - OfferVerifiableCredential, } from "../libs/endorserServer"; import * as libsUtil from "../libs/util"; import { retrieveAccountDids } from "../libs/util"; @@ -202,6 +201,8 @@ import { retrieveAccountDids } from "../libs/util"; }) export default class OfferDetailsView extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; + $route!: RouteLocationNormalizedLoaded; + $router!: Router; activeDid = ""; apiServer = ""; @@ -229,9 +230,9 @@ export default class OfferDetailsView extends Vue { async mounted() { try { - this.prevCredToEdit = (this.$route as Router).query["prevCredToEdit"] + this.prevCredToEdit = (this.$route.query["prevCredToEdit"] as string) ? (JSON.parse( - (this.$route as Router).query["prevCredToEdit"], + (this.$route.query["prevCredToEdit"] as string), ) as GenericCredWrapper<OfferVerifiableCredential>) : undefined; } catch (error) { @@ -249,28 +250,26 @@ export default class OfferDetailsView extends Vue { const prevAmount = this.prevCredToEdit?.claim?.includesObject?.amountOfThisGood; this.amountInput = - (this.$route as Router).query["amountInput"] || + (this.$route.query["amountInput"] as string) || (prevAmount ? String(prevAmount) : "") || this.amountInput; - this.unitCode = ((this.$route as Router).query["unitCode"] || + this.unitCode = ((this.$route.query["unitCode"] as string) || this.prevCredToEdit?.claim?.includesObject?.unitCode || this.unitCode) as string; this.descriptionOfCondition = this.prevCredToEdit?.claim?.description || this.descriptionOfCondition; this.descriptionOfItem = - (this.$route as Router).query["description"] || + (this.$route.query["description"] as string) || this.prevCredToEdit?.claim?.itemOffered?.description || this.descriptionOfItem; - this.destinationPathAfter = (this.$route as Router).query[ - "destinationPathAfter" - ]; - this.offererDid = ((this.$route as Router).query["offererDid"] || - this.prevCredToEdit?.claim?.agent?.identifier || + this.destinationPathAfter = (this.$route.query["destinationPathAfter"] as string) || ""; + this.offererDid = ((this.$route.query["offererDid"] as string) || + (this.prevCredToEdit?.claim?.agent as any)?.identifier || this.offererDid) as string; this.hideBackButton = - (this.$route as Router).query["hideBackButton"] === "true"; - this.message = ((this.$route as Router).query["message"] as string) || ""; + (this.$route.query["hideBackButton"] as string) === "true"; + this.message = ((this.$route.query["message"] as string) || ""); // find any project ID let project; @@ -280,17 +279,17 @@ export default class OfferDetailsView extends Vue { ) { project = this.prevCredToEdit?.claim?.itemOffered?.isPartOf; } - this.projectId = ((this.$route as Router).query["projectId"] || + this.projectId = ((this.$route.query["projectId"] as string) || project?.identifier || this.projectId) as string; - this.projectName = ((this.$route as Router).query["projectName"] || + this.projectName = ((this.$route.query["projectName"] as string) || project?.name || this.projectName) as string; - this.recipientDid = ((this.$route as Router).query["recipientDid"] || + this.recipientDid = ((this.$route.query["recipientDid"] as string) || this.prevCredToEdit?.claim?.recipient?.identifier) as string; this.recipientName = - ((this.$route as Router).query["recipientName"] as string) || ""; + ((this.$route.query["recipientName"] as string) || ""); this.validThroughDateInput = this.prevCredToEdit?.claim?.validThrough || this.validThroughDateInput; diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index 78a776ec..d5798b17 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -496,7 +496,15 @@ import { AxiosError } from "axios"; import { Component, Vue } from "vue-facing-decorator"; import { Router } from "vue-router"; - +import { + GenericVerifiableCredential, + GenericCredWrapper, + GiveSummaryRecord, + GiveVerifiableCredential, + OfferSummaryRecord, + OfferVerifiableCredential, + PlanSummaryRecord, +} from "../interfaces"; import GiftedDialog from "../components/GiftedDialog.vue"; import OfferDialog from "../components/OfferDialog.vue"; import TopMessage from "../components/TopMessage.vue"; @@ -511,14 +519,6 @@ import { } from "../db/index"; import { Contact } from "../db/tables/contacts"; import * as libsUtil from "../libs/util"; -import { - GenericCredWrapper, - GiveSummaryRecord, - GiveVerifiableCredential, - OfferSummaryRecord, - OfferVerifiableCredential, - PlanSummaryRecord, -} from "../libs/endorserServer"; import * as serverUtil from "../libs/endorserServer"; import { retrieveAccountDids } from "../libs/util"; import HiddenDidDialog from "../components/HiddenDidDialog.vue"; @@ -1156,7 +1156,7 @@ export default class ProjectViewView extends Vue { ), ), ); - const confirmationClaim: serverUtil.GenericVerifiableCredential = { + const confirmationClaim: GenericVerifiableCredential = { "@context": "https://schema.org", "@type": "AgreeAction", object: goodClaim,