forked from trent_larson/crowd-funder-for-time-pwa
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
This commit is contained in:
@@ -60,17 +60,21 @@ 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 options = {
|
const sentinelElement = this.$refs.sentinel as HTMLElement;
|
||||||
root: null,
|
// Ensure we have a valid HTMLElement before observing
|
||||||
rootMargin: `0px 0px ${this.distance}px 0px`,
|
if (sentinelElement instanceof HTMLElement) {
|
||||||
threshold: 1.0,
|
const options = {
|
||||||
};
|
root: null,
|
||||||
this.observer = new IntersectionObserver(
|
rootMargin: `0px 0px ${this.distance}px 0px`,
|
||||||
this.handleIntersection,
|
threshold: 1.0,
|
||||||
options,
|
};
|
||||||
);
|
this.observer = new IntersectionObserver(
|
||||||
this.observer.observe(this.$refs.sentinel as HTMLElement);
|
this.handleIntersection,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
this.observer.observe(sentinelElement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 || "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user