From 73806e78bcf548191fc96b42124388dc2b310a3e Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Mon, 3 Nov 2025 19:06:01 -0700 Subject: [PATCH 1/2] refactor: fix the 'back' links to work consistently, so contact pages can be included in other flows --- src/views/ContactEditView.vue | 4 +--- src/views/DIDView.vue | 14 +++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/views/ContactEditView.vue b/src/views/ContactEditView.vue index 51687b5b..a3ec73ce 100644 --- a/src/views/ContactEditView.vue +++ b/src/views/ContactEditView.vue @@ -346,9 +346,7 @@ export default class ContactEditView extends Vue { // Notify success and redirect this.notify.success(NOTIFY_CONTACT_SAVED.message, TIMEOUTS.STANDARD); - (this.$router as Router).push({ - path: "/did/" + encodeURIComponent(this.contact?.did || ""), - }); + this.$router.back(); } } diff --git a/src/views/DIDView.vue b/src/views/DIDView.vue index f6acf31c..8d67961c 100644 --- a/src/views/DIDView.vue +++ b/src/views/DIDView.vue @@ -12,20 +12,20 @@ - - + - - + @@ -476,7 +476,7 @@ export default class DIDView extends Vue { * Navigation helper methods */ goBack() { - this.$router.go(-1); + this.$router.back(); } /** From 7e861e2fca375b7d45951fefd60393b5d339bf31 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Mon, 3 Nov 2025 20:21:34 -0700 Subject: [PATCH 2/2] fix: when organizer adds people, they automatically register them as well --- src/components/BulkMembersDialog.vue | 95 ++++++++++++++++++++------- src/components/MembersList.vue | 29 ++++++-- src/constants/notifications.ts | 8 --- src/interfaces/common.ts | 9 --- src/interfaces/index.ts | 1 + src/libs/endorserServer.ts | 36 +++++----- src/views/ContactsView.vue | 8 ++- src/views/OnboardMeetingSetupView.vue | 8 ++- 8 files changed, 127 insertions(+), 67 deletions(-) diff --git a/src/components/BulkMembersDialog.vue b/src/components/BulkMembersDialog.vue index dd41e474..412ade19 100644 --- a/src/components/BulkMembersDialog.vue +++ b/src/components/BulkMembersDialog.vue @@ -134,8 +134,9 @@ import { Vue, Component, Prop } from "vue-facing-decorator"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; import { SOMEONE_UNNAMED } from "@/constants/entities"; import { MemberData } from "@/interfaces"; -import { setVisibilityUtil, getHeaders } from "@/libs/endorserServer"; +import { setVisibilityUtil, getHeaders, register } from "@/libs/endorserServer"; import { createNotifyHelpers } from "@/utils/notify"; +import { Contact } from "@/db/tables/contacts"; @Component({ mixins: [PlatformServiceMixin], @@ -253,33 +254,37 @@ export default class BulkMembersDialog extends Vue { async handleMainAction() { if (this.dialogType === "admit") { - await this.admitWithVisibility(); + await this.organizerAdmitAndAddWithVisibility(); } else { - await this.addContactWithVisibility(); + await this.memberAddContactWithVisibility(); } } - async admitWithVisibility() { + async organizerAdmitAndAddWithVisibility() { try { - const selectedMembers = this.membersData.filter((member) => + const selectedMembers: MemberData[] = this.membersData.filter((member) => this.selectedMembers.includes(member.did), ); - const notSelectedMembers = this.membersData.filter( + const notSelectedMembers: MemberData[] = this.membersData.filter( (member) => !this.selectedMembers.includes(member.did), ); let admittedCount = 0; let contactAddedCount = 0; + let errors = 0; for (const member of selectedMembers) { try { // First, admit the member await this.admitMember(member); + + // Register them + await this.registerMember(member); admittedCount++; // If they're not a contact yet, add them as a contact if (!member.isContact) { - await this.addAsContact(member); + await this.addAsContact(member, true); contactAddedCount++; } @@ -289,19 +294,33 @@ export default class BulkMembersDialog extends Vue { // eslint-disable-next-line no-console console.error(`Error processing member ${member.did}:`, error); // Continue with other members even if one fails + errors++; } } // Show success notification - this.$notify( - { - group: "alert", - type: "success", - title: "Members Admitted Successfully", - text: `${admittedCount} member${admittedCount === 1 ? "" : "s"} admitted${contactAddedCount === 0 ? "" : admittedCount === contactAddedCount ? " and" : `, ${contactAddedCount}`}${contactAddedCount === 0 ? "" : ` added as contact${contactAddedCount === 1 ? "" : "s"}`}.`, - }, - 10000, - ); + if (admittedCount > 0) { + this.$notify( + { + group: "alert", + type: "success", + title: "Members Admitted Successfully", + text: `${admittedCount} member${admittedCount === 1 ? "" : "s"} admitted and registered${contactAddedCount === 0 ? "" : admittedCount === contactAddedCount ? " and" : `, ${contactAddedCount}`}${contactAddedCount === 0 ? "" : ` added as contact${contactAddedCount === 1 ? "" : "s"}`}.`, + }, + 10000, + ); + } + if (errors > 0) { + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: "Failed to fully admit some members. Work with them individually below.", + }, + 5000, + ); + } this.close(notSelectedMembers.map((member) => member.did)); } catch (error) { @@ -312,19 +331,19 @@ export default class BulkMembersDialog extends Vue { group: "alert", type: "danger", title: "Error", - text: "Failed to admit some members. Please try again.", + text: "Some errors occurred. Work with members individually below.", }, 5000, ); } } - async addContactWithVisibility() { + async memberAddContactWithVisibility() { try { - const selectedMembers = this.membersData.filter((member) => + const selectedMembers: MemberData[] = this.membersData.filter((member) => this.selectedMembers.includes(member.did), ); - const notSelectedMembers = this.membersData.filter( + const notSelectedMembers: MemberData[] = this.membersData.filter( (member) => !this.selectedMembers.includes(member.did), ); @@ -334,7 +353,7 @@ export default class BulkMembersDialog extends Vue { try { // If they're not a contact yet, add them as a contact first if (!member.isContact) { - await this.addAsContact(member); + await this.addAsContact(member, undefined); contactsAddedCount++; } @@ -367,7 +386,7 @@ export default class BulkMembersDialog extends Vue { group: "alert", type: "danger", title: "Error", - text: "Failed to add some members as contacts. Please try again.", + text: "Some errors occurred. Work with members individually below.", }, 5000, ); @@ -393,11 +412,39 @@ export default class BulkMembersDialog extends Vue { } } - async addAsContact(member: { did: string; name: string }) { + async registerMember(member: MemberData) { + try { + const contact: Contact = { did: member.did }; + const result = await register( + this.activeDid, + this.apiServer, + this.axios, + contact, + ); + if (result.success) { + if (result.embeddedRecordError) { + throw new Error(result.embeddedRecordError); + } + await this.$updateContact(member.did, { registered: true }); + } else { + throw result; + } + } catch (err) { + // eslint-disable-next-line no-console + console.error("Error registering member:", err); + throw err; + } + } + + async addAsContact( + member: { did: string; name: string }, + isRegistered?: boolean, + ) { try { - const newContact = { + const newContact: Contact = { did: member.did, name: member.name, + registered: isRegistered, }; await this.$insertContact(newContact); diff --git a/src/components/MembersList.vue b/src/components/MembersList.vue index f0043040..d1b10567 100644 --- a/src/components/MembersList.vue +++ b/src/components/MembersList.vue @@ -99,7 +99,7 @@ +
+ + + + + + +
(url, { jwtEncoded: vcJwt }); - if (resp.data?.success?.handleId) { - return { success: true }; - } else if (resp.data?.success?.embeddedRecordError) { + if (resp.data?.success?.embeddedRecordError) { let message = "There was some problem with the registration and so it may not be complete."; if (typeof resp.data.success.embeddedRecordError === "string") { message += " " + resp.data.success.embeddedRecordError; } return { error: message }; + } else if (resp.data?.success?.handleId) { + return { success: true }; } else { - logger.error("Registration error:", JSON.stringify(resp.data)); - return { error: "Got a server error when registering." }; + logger.error("Registration non-thrown error:", JSON.stringify(resp.data)); + return { + error: + (resp.data?.error as { message?: string })?.message || + (resp.data?.error as string) || + "Got a server error when registering.", + }; } } catch (error: unknown) { if (error && typeof error === "object") { const err = error as AxiosErrorResponse; const errorMessage = - err.message || - (err.response?.data && - typeof err.response.data === "object" && - "message" in err.response.data - ? (err.response.data as { message: string }).message - : undefined); - logger.error("Registration error:", errorMessage || JSON.stringify(err)); + err.response?.data?.error?.message || + err.response?.data?.error || + err.message; + logger.error( + "Registration thrown error:", + errorMessage || JSON.stringify(err), + ); return { error: errorMessage || "Got a server error when registering." }; } return { error: "Got a server error when registering." }; diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index e31cb708..eebd8049 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -171,9 +171,11 @@ import { CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI, CONTACT_URL_PATH_ENDORSER_CH_OLD, } from "../libs/endorserServer"; -import { GiveSummaryRecord } from "@/interfaces/records"; -import { UserInfo } from "@/interfaces/common"; -import { VerifiableCredential } from "@/interfaces/claims-result"; +import { + GiveSummaryRecord, + UserInfo, + VerifiableCredential, +} from "@/interfaces"; import * as libsUtil from "../libs/util"; import { generateSaveAndActivateIdentity, diff --git a/src/views/OnboardMeetingSetupView.vue b/src/views/OnboardMeetingSetupView.vue index e70148f5..33d345f0 100644 --- a/src/views/OnboardMeetingSetupView.vue +++ b/src/views/OnboardMeetingSetupView.vue @@ -473,6 +473,7 @@ export default class OnboardMeetingView extends Vue { ); return; } + const password: string = this.newOrUpdatedMeetingInputs.password; // create content with user's name & DID encrypted with password const content = { @@ -482,7 +483,7 @@ export default class OnboardMeetingView extends Vue { }; const encryptedContent = await encryptMessage( JSON.stringify(content), - this.newOrUpdatedMeetingInputs.password, + password, ); const headers = await getHeaders(this.activeDid); @@ -505,6 +506,11 @@ export default class OnboardMeetingView extends Vue { this.newOrUpdatedMeetingInputs = null; this.notify.success(NOTIFY_MEETING_CREATED.message, TIMEOUTS.STANDARD); + // redirect to the same page with the password parameter set + this.$router.push({ + name: "onboard-meeting-setup", + query: { password: password }, + }); } else { throw { response: response }; }