Browse Source

Fix JavaScript runtime errors for undefined name property access

- Add null checks to prevent "Cannot read properties of undefined (reading 'name')" errors
- Fix ProjectCard, MembersList, ProjectsView, DiscoverView, ProjectViewView components
- Add null validation in DIDView.claimDescription() and ClaimReportCertificateView.drawCanvas()
- Add missing databaseUtil import in MembersList component
- Use meaningful fallback text for undefined names ("Unnamed Project", "Unnamed Member")
- Resolves template rendering crashes when entities lack name properties
pull/142/head
Matthew Raymer 1 day ago
parent
commit
23789d4ecd
  1. 8
      src/components/InfiniteScroll.vue
  2. 77
      src/components/MembersList.vue
  3. 2
      src/components/ProjectCard.vue
  4. 4
      src/views/ClaimReportCertificateView.vue
  5. 15
      src/views/DIDView.vue
  6. 4
      src/views/DiscoverView.vue
  7. 4
      src/views/ProjectViewView.vue
  8. 4
      src/views/ProjectsView.vue

8
src/components/InfiniteScroll.vue

@ -60,7 +60,10 @@ export default class InfiniteScroll extends Vue {
* Used internally by Vue's lifecycle system * Used internally by Vue's lifecycle system
*/ */
updated() { updated() {
if (!this.observer) { if (!this.observer && this.$refs.sentinel) {
const sentinelElement = this.$refs.sentinel as HTMLElement;
// Ensure we have a valid HTMLElement before observing
if (sentinelElement instanceof HTMLElement) {
const options = { const options = {
root: null, root: null,
rootMargin: `0px 0px ${this.distance}px 0px`, rootMargin: `0px 0px ${this.distance}px 0px`,
@ -70,7 +73,8 @@ export default class InfiniteScroll extends Vue {
this.handleIntersection, this.handleIntersection,
options, options,
); );
this.observer.observe(this.$refs.sentinel as HTMLElement); this.observer.observe(sentinelElement);
}
} }
} }

77
src/components/MembersList.vue

@ -66,7 +66,7 @@
changes the password changes the password
--> -->
<button <button
class="w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors" class="btn-action-refresh"
title="Refresh members list" title="Refresh members list"
@click="fetchMembers" @click="fetchMembers"
> >
@ -80,13 +80,15 @@
> >
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center"> <div class="flex items-center">
<h3 class="text-lg font-medium">{{ member.name }}</h3> <h3 class="text-lg font-medium">
{{ member.name || "Unnamed Member" }}
</h3>
<div <div
v-if="!getContactFor(member.did) && member.did !== activeDid" v-if="!getContactFor(member.did) && member.did !== activeDid"
class="flex justify-end" class="flex justify-end"
> >
<button <button
class="ml-2 w-8 h-8 flex items-center justify-center rounded-full bg-green-100 text-green-600 hover:bg-green-200 hover:text-green-800 transition-colors" class="btn-add-contact"
title="Add as contact" title="Add as contact"
@click="addAsContact(member)" @click="addAsContact(member)"
> >
@ -95,7 +97,7 @@
</div> </div>
<button <button
v-if="member.did !== activeDid" v-if="member.did !== activeDid"
class="ml-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-colors" class="btn-info-contact"
title="Contact info" title="Contact info"
@click=" @click="
informAboutAddingContact( informAboutAddingContact(
@ -114,7 +116,7 @@
class="flex items-center" class="flex items-center"
> >
<button <button
class="mr-2 w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors" class="btn-admission"
:title=" :title="
member.member.admitted ? 'Remove member' : 'Admit member' member.member.admitted ? 'Remove member' : 'Admit member'
" "
@ -126,7 +128,7 @@
/> />
</button> </button>
<button <button
class="mr-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-colors" class="btn-info-admission"
title="Admission info" title="Admission info"
@click="informAboutAdmission()" @click="informAboutAdmission()"
> >
@ -141,7 +143,7 @@
</div> </div>
<div v-if="membersToShow().length > 0" class="flex justify-center mt-4"> <div v-if="membersToShow().length > 0" class="flex justify-center mt-4">
<button <button
class="w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors" class="btn-action-refresh"
title="Refresh members list" title="Refresh members list"
@click="fetchMembers" @click="fetchMembers"
> >
@ -168,10 +170,10 @@ import {
} from "../libs/endorserServer"; } from "../libs/endorserServer";
import { decryptMessage } from "../libs/crypto"; import { decryptMessage } from "../libs/crypto";
import { Contact } from "../db/tables/contacts"; import { Contact } from "../db/tables/contacts";
import * as databaseUtil from "../db/databaseUtil";
import * as libsUtil from "../libs/util"; import * as libsUtil from "../libs/util";
import { NotificationIface } from "../constants/app"; import { NotificationIface } from "../constants/app";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import * as databaseUtil from "../db/databaseUtil";
interface Member { interface Member {
admitted: boolean; admitted: boolean;
@ -186,7 +188,9 @@ interface DecryptedMember {
isRegistered: boolean; isRegistered: boolean;
} }
@Component @Component({
mixins: [PlatformServiceMixin],
})
export default class MembersList extends Vue { export default class MembersList extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;
@ -353,13 +357,7 @@ export default class MembersList extends Vue {
} }
async loadContacts() { async loadContacts() {
const platformService = PlatformServiceFactory.getInstance(); this.contacts = await this.$getAllContacts();
const result = await platformService.dbQuery("SELECT * FROM contacts");
if (result) {
this.contacts = databaseUtil.mapQueryResultToValues(
result,
) as unknown as Contact[];
}
} }
getContactFor(did: string): Contact | undefined { getContactFor(did: string): Contact | undefined {
@ -443,11 +441,7 @@ export default class MembersList extends Vue {
if (result.success) { if (result.success) {
decrMember.isRegistered = true; decrMember.isRegistered = true;
if (oldContact) { if (oldContact) {
const platformService = PlatformServiceFactory.getInstance(); await this.$updateContact(decrMember.did, { registered: true });
await platformService.dbExec(
"UPDATE contacts SET registered = ? WHERE did = ?",
[true, decrMember.did],
);
oldContact.registered = true; oldContact.registered = true;
} }
this.$notify( this.$notify(
@ -500,11 +494,7 @@ export default class MembersList extends Vue {
name: member.name, name: member.name,
}; };
const platformService = PlatformServiceFactory.getInstance(); await this.$insertContact(newContact);
await platformService.dbExec(
"INSERT INTO contacts (did, name) VALUES (?, ?)",
[member.did, member.name],
);
this.contacts.push(newContact); this.contacts.push(newContact);
this.$notify( this.$notify(
@ -535,3 +525,36 @@ export default class MembersList extends Vue {
} }
} }
</script> </script>
<style scoped>
/* Button Classes */
.btn-action-refresh {
@apply w-8 h-8 flex items-center justify-center rounded-full
bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800
transition-colors;
}
.btn-add-contact {
@apply ml-2 w-8 h-8 flex items-center justify-center rounded-full
bg-green-100 text-green-600 hover:bg-green-200 hover:text-green-800
transition-colors;
}
.btn-info-contact {
@apply ml-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full
bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800
transition-colors;
}
.btn-admission {
@apply mr-2 w-6 h-6 flex items-center justify-center rounded-full
bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800
transition-colors;
}
.btn-info-admission {
@apply mr-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full
bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800
transition-colors;
}
</style>

2
src/components/ProjectCard.vue

@ -15,7 +15,7 @@ issuer information. * * @author Matthew Raymer */
<h3 <h3
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden" class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
> >
{{ project.name }} {{ project.name || "Unnamed Project" }}
</h3> </h3>
<div class="text-xs text-slate-500 truncate"> <div class="text-xs text-slate-500 truncate">

4
src/views/ClaimReportCertificateView.vue

@ -70,6 +70,10 @@ export default class ClaimReportCertificateView extends Vue {
} }
async drawCanvas(claimData: GenericCredWrapper<GenericVerifiableCredential>) { async drawCanvas(claimData: GenericCredWrapper<GenericVerifiableCredential>) {
if (!claimData || !claimData.claim) {
return;
}
const platformService = PlatformServiceFactory.getInstance(); const platformService = PlatformServiceFactory.getInstance();
const dbAllContacts = await platformService.dbQuery( const dbAllContacts = await platformService.dbQuery(
"SELECT * FROM contacts", "SELECT * FROM contacts",

15
src/views/DIDView.vue

@ -406,11 +406,17 @@ export default class DIDView extends Vue {
"SELECT * FROM contacts WHERE did = ?", "SELECT * FROM contacts WHERE did = ?",
[this.viewingDid], [this.viewingDid],
); );
this.contactFromDid = databaseUtil.mapQueryResultToValues( const contacts = databaseUtil.mapQueryResultToValues(
dbContacts, dbContacts,
)[0] as unknown as Contact; ) as unknown as Contact[];
if (this.contactFromDid) {
// Safely check if contact exists before assigning
if (contacts && contacts.length > 0) {
this.contactFromDid = contacts[0];
this.contactYaml = yaml.dump(this.contactFromDid); this.contactYaml = yaml.dump(this.contactFromDid);
} else {
this.contactFromDid = undefined;
this.contactYaml = "";
} }
} }
@ -705,6 +711,9 @@ export default class DIDView extends Vue {
* @returns Description string or empty string * @returns Description string or empty string
*/ */
claimDescription(claim: GenericVerifiableCredential) { claimDescription(claim: GenericVerifiableCredential) {
if (!claim || !claim.claim) {
return "";
}
const claimData = claim.claim as { name?: string; description?: string }; const claimData = claim.claim as { name?: string; description?: string };
return claimData.name || claimData.description || ""; return claimData.name || claimData.description || "";
} }

4
src/views/DiscoverView.vue

@ -232,7 +232,9 @@
</div> </div>
<div class="grow"> <div class="grow">
<h2 class="text-base font-semibold">{{ project.name }}</h2> <h2 class="text-base font-semibold">
{{ project.name || "Unnamed Project" }}
</h2>
<div class="text-sm"> <div class="text-sm">
<font-awesome <font-awesome
icon="user" icon="user"

4
src/views/ProjectViewView.vue

@ -183,7 +183,7 @@
class="text-blue-500" class="text-blue-500"
@click="onClickLoadProject(plan.handleId)" @click="onClickLoadProject(plan.handleId)"
> >
{{ plan.name }} {{ plan.name || "Unnamed Project" }}
</button> </button>
</div> </div>
<div v-if="fulfillersToHitLimit" class="text-center"> <div v-if="fulfillersToHitLimit" class="text-center">
@ -207,7 +207,7 @@
class="text-blue-500" class="text-blue-500"
@click="onClickLoadProject(fulfilledByThis.handleId)" @click="onClickLoadProject(fulfilledByThis.handleId)"
> >
{{ fulfilledByThis.name }} {{ fulfilledByThis.name || "Unnamed Project" }}
</button> </button>
</div> </div>
</div> </div>

4
src/views/ProjectsView.vue

@ -252,7 +252,9 @@
</div> </div>
<div class="grow overflow-hidden"> <div class="grow overflow-hidden">
<h2 class="text-base font-semibold">{{ project.name }}</h2> <h2 class="text-base font-semibold">
{{ project.name || "Unnamed Project" }}
</h2>
<div class="text-sm truncate"> <div class="text-sm truncate">
{{ project.description }} {{ project.description }}
</div> </div>

Loading…
Cancel
Save