forked from trent_larson/crowd-funder-for-time-pwa
Refactor ClaimView.vue: remove inline template logic, improve types, and centralize logic
- Move all complex template logic to computed properties and methods - Replace all `as any` usages with proper TypeScript types (OfferClaim, GiveActionClaim) - Add computed property for claim image, removing inline image access - Route all logging through PlatformServiceMixin - Ensure all icon-only buttons have aria-labels for accessibility - Remove unused imports and direct logger usage - Lint clean: no warnings or errors remain
This commit is contained in:
@@ -345,7 +345,6 @@ interface Settings {
|
||||
})
|
||||
export default class App extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
$logAndConsole!: (message: string, isError?: boolean) => Promise<void>;
|
||||
|
||||
stopAsking = false;
|
||||
|
||||
|
||||
@@ -1062,6 +1062,39 @@ export const PlatformServiceMixin = {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// =================================================
|
||||
// LOGGING METHODS (convenience methods for components)
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Log message to database - $log()
|
||||
* @param message Message to log
|
||||
* @param level Log level (info, warn, error)
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async $log(message: string, level?: string): Promise<void> {
|
||||
return logger.toDb(message, level);
|
||||
},
|
||||
|
||||
/**
|
||||
* Log error message to database - $logError()
|
||||
* @param message Error message to log
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async $logError(message: string): Promise<void> {
|
||||
return logger.toDb(message, "error");
|
||||
},
|
||||
|
||||
/**
|
||||
* Log message to console and database - $logAndConsole()
|
||||
* @param message Message to log
|
||||
* @param isError Whether this is an error message
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async $logAndConsole(message: string, isError = false): Promise<void> {
|
||||
return logger.toConsoleAndDb(message, isError);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1123,6 +1156,11 @@ export interface IPlatformServiceMixin {
|
||||
did: string,
|
||||
settings: Partial<Settings>,
|
||||
): Promise<boolean>;
|
||||
|
||||
// Logging methods
|
||||
$log(message: string, level?: string): Promise<void>;
|
||||
$logError(message: string): Promise<void>;
|
||||
$logAndConsole(message: string, isError?: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
// TypeScript declaration merging to eliminate (this as any) type assertions
|
||||
@@ -1217,5 +1255,10 @@ declare module "@vue/runtime-core" {
|
||||
did: string,
|
||||
settings: Partial<Settings>,
|
||||
): Promise<boolean>;
|
||||
|
||||
// Logging methods
|
||||
$log(message: string, level?: string): Promise<void>;
|
||||
$logError(message: string): Promise<void>;
|
||||
$logAndConsole(message: string, isError?: boolean): Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<!-- Back -->
|
||||
<button
|
||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||
aria-label="Go back"
|
||||
@click="$router.go(-1)"
|
||||
>
|
||||
<font-awesome icon="chevron-left" class="fa-fw" />
|
||||
@@ -26,14 +27,7 @@
|
||||
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
|
||||
"
|
||||
v-if="canEditClaim"
|
||||
title="Edit"
|
||||
data-testId="editClaimButton"
|
||||
@click="onClickEditClaim"
|
||||
@@ -60,6 +54,7 @@
|
||||
v-if="veriClaim.id"
|
||||
class="text-blue-500 ml-2 mt-2"
|
||||
title="Copy Printable Certificate Link"
|
||||
aria-label="Copy printable certificate link"
|
||||
@click="
|
||||
copyToClipboard(
|
||||
'A link to the certificate page',
|
||||
@@ -74,6 +69,7 @@
|
||||
<div class="flex justify-end w-full">
|
||||
<button
|
||||
title="Copy Link"
|
||||
aria-label="Copy page link"
|
||||
@click="copyToClipboard('A link to this page', windowDeepLink)"
|
||||
>
|
||||
<font-awesome icon="link" class="text-slate-500" />
|
||||
@@ -83,11 +79,7 @@
|
||||
<div class="text-sm">
|
||||
<div data-testId="description">
|
||||
<font-awesome icon="message" class="fa-fw text-slate-400" />
|
||||
{{
|
||||
(veriClaim.claim?.itemOffered as any)?.description ||
|
||||
(veriClaim.claim as any)?.description ||
|
||||
""
|
||||
}}
|
||||
{{ claimDescription }}
|
||||
</div>
|
||||
<div>
|
||||
<font-awesome icon="user" class="fa-fw text-slate-400" />
|
||||
@@ -96,17 +88,11 @@
|
||||
<div>
|
||||
<font-awesome icon="calendar" class="fa-fw text-slate-400" />
|
||||
Recorded
|
||||
{{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }}
|
||||
{{ formattedIssueDate }}
|
||||
</div>
|
||||
<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"
|
||||
/>
|
||||
<div v-if="claimImage" class="flex justify-center">
|
||||
<a :href="claimImage" target="_blank">
|
||||
<img :src="claimImage" class="h-24 rounded-xl" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -183,14 +169,7 @@
|
||||
<div class="grow overflow-hidden">
|
||||
<a
|
||||
class="text-blue-500 mt-4 cursor-pointer"
|
||||
@click="
|
||||
provider.identifier.startsWith('did:')
|
||||
? $router.push(
|
||||
'/did/' +
|
||||
encodeURIComponent(provider.identifier),
|
||||
)
|
||||
: showDifferentClaimPage(provider.identifier)
|
||||
"
|
||||
@click="handleProviderClick(provider)"
|
||||
>
|
||||
an activity...
|
||||
</a>
|
||||
@@ -266,13 +245,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span>
|
||||
<span v-else-if="totalConfirmers() === 1">
|
||||
One person has confirmed this.
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ totalConfirmers() }} people have confirmed this.
|
||||
</span>
|
||||
{{ confirmationStatusText }}
|
||||
</div>
|
||||
|
||||
<div v-if="totalConfirmers() > 0">
|
||||
@@ -392,13 +365,7 @@
|
||||
<font-awesome v-else icon="chevron-right" />
|
||||
</h2>
|
||||
<div v-if="showVeriClaimDump">
|
||||
<div
|
||||
v-if="
|
||||
serverUtil.containsHiddenDid(veriClaim) &&
|
||||
R.isEmpty(veriClaimDidsVisible)
|
||||
"
|
||||
class="mb-2"
|
||||
>
|
||||
<div v-if="isFullyHidden" class="mb-2">
|
||||
Some of the details are not visible to you; they show as "HIDDEN". They
|
||||
are not visible to any of your direct contacts, either.
|
||||
<span v-if="canShare">
|
||||
@@ -425,7 +392,7 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="!R.isEmpty(veriClaimDidsVisible)">
|
||||
<div v-if="hasPartialVisibility">
|
||||
Some of the details are not visible to you but they are visible to some
|
||||
of your contacts.
|
||||
<span v-if="canShare">
|
||||
@@ -484,11 +451,7 @@
|
||||
class="text-blue-500"
|
||||
>
|
||||
<font-awesome icon="globe" class="fa-fw" />
|
||||
{{
|
||||
veriClaim.publicUrls[visDid].substring(
|
||||
veriClaim.publicUrls[visDid].indexOf("//") + 2,
|
||||
)
|
||||
}}
|
||||
{{ extractDomain(veriClaim.publicUrls[visDid] || "") }}
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
@@ -560,9 +523,13 @@ import QuickNav from "../components/QuickNav.vue";
|
||||
import { APP_SERVER, NotificationIface } from "../constants/app";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import * as serverUtil from "../libs/endorserServer";
|
||||
import { GenericCredWrapper, OfferClaim, ProviderInfo } from "../interfaces";
|
||||
import {
|
||||
GenericCredWrapper,
|
||||
OfferClaim,
|
||||
GiveActionClaim,
|
||||
ProviderInfo,
|
||||
} from "../interfaces";
|
||||
import * as libsUtil from "../libs/util";
|
||||
import { logger } from "../utils/logger";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
|
||||
@Component({
|
||||
@@ -609,7 +576,102 @@ export default class ClaimView extends Vue {
|
||||
yaml = yaml;
|
||||
libsUtil = libsUtil;
|
||||
serverUtil = serverUtil;
|
||||
window = window;
|
||||
|
||||
// =================================================
|
||||
// COMPUTED PROPERTIES
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Whether the current user can edit this claim
|
||||
*/
|
||||
get canEditClaim(): boolean {
|
||||
return (
|
||||
["GiveAction", "Offer", "PlanAction"].includes(
|
||||
this.veriClaim.claimType as string,
|
||||
) && this.veriClaim.issuer === this.activeDid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The description to display for this claim
|
||||
*/
|
||||
get claimDescription(): string {
|
||||
const claim = this.veriClaim.claim;
|
||||
|
||||
// Handle Offer claims with itemOffered
|
||||
if (this.veriClaim.claimType === "Offer") {
|
||||
const offerClaim = claim as OfferClaim;
|
||||
return (
|
||||
offerClaim.itemOffered?.description || offerClaim.description || ""
|
||||
);
|
||||
}
|
||||
|
||||
// Handle GiveAction claims
|
||||
if (this.veriClaim.claimType === "GiveAction") {
|
||||
const giveClaim = claim as GiveActionClaim;
|
||||
return giveClaim.description || "";
|
||||
}
|
||||
|
||||
// Fallback for other claim types
|
||||
return (claim as { description?: string })?.description || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted issue date for display
|
||||
*/
|
||||
get formattedIssueDate(): string {
|
||||
return (
|
||||
this.veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") || ""
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Text describing the confirmation status
|
||||
*/
|
||||
get confirmationStatusText(): string {
|
||||
const count = this.totalConfirmers();
|
||||
if (count === 0) return "Nobody has confirmed this.";
|
||||
if (count === 1) return "One person has confirmed this.";
|
||||
return `${count} people have confirmed this.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the claim is fully hidden from the user
|
||||
*/
|
||||
get isFullyHidden(): boolean {
|
||||
return (
|
||||
serverUtil.containsHiddenDid(this.veriClaim) &&
|
||||
R.isEmpty(this.veriClaimDidsVisible)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the claim has some visibility through contacts
|
||||
*/
|
||||
get hasPartialVisibility(): boolean {
|
||||
return !R.isEmpty(this.veriClaimDidsVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* The image URL for this claim if available
|
||||
*/
|
||||
get claimImage(): string | undefined {
|
||||
const claim = this.veriClaim.claim;
|
||||
|
||||
// Handle different claim types that might have images
|
||||
if (this.veriClaim.claimType === "Offer") {
|
||||
const offerClaim = claim as OfferClaim;
|
||||
return offerClaim.image;
|
||||
}
|
||||
|
||||
if (this.veriClaim.claimType === "GiveAction") {
|
||||
const giveClaim = claim as GiveActionClaim;
|
||||
return giveClaim.image;
|
||||
}
|
||||
|
||||
// Fallback for other claim types
|
||||
return (claim as { image?: string })?.image;
|
||||
}
|
||||
|
||||
resetThisValues() {
|
||||
this.confirmerIdList = [];
|
||||
@@ -628,6 +690,28 @@ export default class ClaimView extends Vue {
|
||||
this.veriClaimDidsVisible = {};
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// UTILITY METHODS
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Handle provider click navigation
|
||||
*/
|
||||
handleProviderClick(provider: ProviderInfo): void {
|
||||
if (provider.identifier.startsWith("did:")) {
|
||||
this.$router.push("/did/" + encodeURIComponent(provider.identifier));
|
||||
} else {
|
||||
this.showDifferentClaimPage(provider.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract domain from URL for display
|
||||
*/
|
||||
extractDomain(url: string): string {
|
||||
return url.substring(url.indexOf("//") + 2);
|
||||
}
|
||||
|
||||
async created() {
|
||||
const settings = await this.$settings();
|
||||
|
||||
@@ -640,7 +724,7 @@ export default class ClaimView extends Vue {
|
||||
try {
|
||||
this.allMyDids = await libsUtil.retrieveAccountDids();
|
||||
} catch (error) {
|
||||
await logger.toConsoleAndDb(
|
||||
await this.$logAndConsole(
|
||||
"Error retrieving all account DIDs on home page:" + error,
|
||||
true,
|
||||
);
|
||||
@@ -676,10 +760,9 @@ export default class ClaimView extends Vue {
|
||||
|
||||
// insert a space before any capital letters except the initial letter
|
||||
// (and capitalize initial letter, just in case)
|
||||
capitalizeAndInsertSpacesBeforeCaps(text: string) {
|
||||
return !text
|
||||
? ""
|
||||
: text[0].toUpperCase() + text.substr(1).replace(/([A-Z])/g, " $1");
|
||||
capitalizeAndInsertSpacesBeforeCaps(text: string): string {
|
||||
if (!text) return "";
|
||||
return text[0].toUpperCase() + text.substr(1).replace(/([A-Z])/g, " $1");
|
||||
}
|
||||
|
||||
totalConfirmers() {
|
||||
@@ -691,7 +774,7 @@ export default class ClaimView extends Vue {
|
||||
}
|
||||
|
||||
// Isn't there a better way to make this available to the template?
|
||||
didInfo(did: string) {
|
||||
didInfo(did: string): string {
|
||||
return serverUtil.didInfo(
|
||||
did,
|
||||
this.activeDid,
|
||||
@@ -719,7 +802,7 @@ export default class ClaimView extends Vue {
|
||||
);
|
||||
} else {
|
||||
// actually, axios typically throws an error so we never get here
|
||||
logger.error("Error getting claim:", resp);
|
||||
await this.$logError("Error getting claim: " + JSON.stringify(resp));
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -747,7 +830,9 @@ export default class ClaimView extends Vue {
|
||||
if (giveResp.status === 200 && giveResp.data.data?.length > 0) {
|
||||
this.detailsForGive = giveResp.data.data[0];
|
||||
} else {
|
||||
logger.error("Error getting detailed give info:", giveResp);
|
||||
await this.$logError(
|
||||
"Error getting detailed give info: " + JSON.stringify(giveResp),
|
||||
);
|
||||
}
|
||||
|
||||
// look for providers
|
||||
@@ -766,7 +851,9 @@ export default class ClaimView extends Vue {
|
||||
) {
|
||||
this.providersForGive = providerResp.data.data;
|
||||
} else {
|
||||
logger.error("Error getting give providers:", giveResp);
|
||||
await this.$logError(
|
||||
"Error getting give providers: " + JSON.stringify(giveResp),
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -789,7 +876,9 @@ export default class ClaimView extends Vue {
|
||||
if (offerResp.status === 200) {
|
||||
this.detailsForOffer = offerResp.data.data[0];
|
||||
} else {
|
||||
logger.error("Error getting detailed offer info:", offerResp);
|
||||
await this.$logError(
|
||||
"Error getting detailed offer info: " + JSON.stringify(offerResp),
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -819,7 +908,9 @@ export default class ClaimView extends Vue {
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const serverError = error as AxiosError;
|
||||
logger.error("Error retrieving claim:", serverError);
|
||||
await this.$logError(
|
||||
"Error retrieving claim: " + JSON.stringify(serverError),
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -844,7 +935,9 @@ export default class ClaimView extends Vue {
|
||||
this.fullClaimDump = yaml.dump(this.fullClaim);
|
||||
} else {
|
||||
// actually, axios typically throws an error so we never get here
|
||||
logger.error("Error getting full claim:", resp);
|
||||
await this.$logError(
|
||||
"Error getting full claim: " + JSON.stringify(resp),
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -856,7 +949,9 @@ export default class ClaimView extends Vue {
|
||||
);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
logger.error("Error retrieving full claim:", error);
|
||||
await this.$logError(
|
||||
"Error retrieving full claim: " + JSON.stringify(error),
|
||||
);
|
||||
const serverError = error as AxiosError;
|
||||
if (serverError.response?.status === 403) {
|
||||
let issuerPhrase = "";
|
||||
@@ -951,7 +1046,9 @@ export default class ClaimView extends Vue {
|
||||
5000,
|
||||
);
|
||||
} else {
|
||||
logger.error("Got error submitting the confirmation:", result);
|
||||
await this.$logError(
|
||||
"Got error submitting the confirmation: " + JSON.stringify(result),
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -1013,7 +1110,7 @@ export default class ClaimView extends Vue {
|
||||
});
|
||||
}
|
||||
|
||||
onClickEditClaim() {
|
||||
async onClickEditClaim() {
|
||||
if (this.veriClaim.claimType === "GiveAction") {
|
||||
const route = {
|
||||
name: "gifted-details",
|
||||
@@ -1041,9 +1138,8 @@ export default class ClaimView extends Vue {
|
||||
};
|
||||
(this.$router as Router).push(route);
|
||||
} else {
|
||||
logger.error(
|
||||
"Unrecognized claim type for edit:",
|
||||
this.veriClaim.claimType,
|
||||
await this.$logError(
|
||||
"Unrecognized claim type for edit: " + this.veriClaim.claimType,
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user