Compare commits
4 Commits
contact-pa
...
bulk-membe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c06ffec466 | ||
|
|
8b199ec76c | ||
| 7e861e2fca | |||
| 73806e78bc |
@@ -111,7 +111,7 @@
|
||||
? 'bg-blue-600 text-white cursor-pointer'
|
||||
: 'bg-slate-400 text-slate-200 cursor-not-allowed',
|
||||
]"
|
||||
@click="handleMainAction"
|
||||
@click="processSelectedMembers"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</button>
|
||||
@@ -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],
|
||||
@@ -144,7 +145,7 @@ import { createNotifyHelpers } from "@/utils/notify";
|
||||
export default class BulkMembersDialog extends Vue {
|
||||
@Prop({ default: "" }) activeDid!: string;
|
||||
@Prop({ default: "" }) apiServer!: string;
|
||||
@Prop({ required: true }) dialogType!: "admit" | "visibility";
|
||||
// isOrganizer: true = organizer mode (admit members), false = member mode (set visibility)
|
||||
@Prop({ required: true }) isOrganizer!: boolean;
|
||||
|
||||
// Vue notification system
|
||||
@@ -251,35 +252,35 @@ export default class BulkMembersDialog extends Vue {
|
||||
return this.selectedMembers.includes(memberDid);
|
||||
}
|
||||
|
||||
async handleMainAction() {
|
||||
if (this.dialogType === "admit") {
|
||||
await this.admitWithVisibility();
|
||||
} else {
|
||||
await this.addContactWithVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
async admitWithVisibility() {
|
||||
async processSelectedMembers() {
|
||||
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);
|
||||
admittedCount++;
|
||||
// Organizer mode: admit and register the member first
|
||||
if (this.isOrganizer) {
|
||||
await this.admitMember(member);
|
||||
await this.registerMember(member);
|
||||
admittedCount++;
|
||||
}
|
||||
|
||||
// If they're not a contact yet, add them as a contact
|
||||
if (!member.isContact) {
|
||||
await this.addAsContact(member);
|
||||
// Organizer mode: set isRegistered to true, member mode: undefined
|
||||
await this.addAsContact(
|
||||
member,
|
||||
this.isOrganizer ? true : undefined,
|
||||
);
|
||||
contactAddedCount++;
|
||||
}
|
||||
|
||||
@@ -289,85 +290,62 @@ 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 (this.isOrganizer) {
|
||||
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"}`}.`,
|
||||
},
|
||||
5000,
|
||||
);
|
||||
}
|
||||
if (errors > 0) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "Failed to fully admit some members. Work with them individually below.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Member mode: show contacts added notification
|
||||
if (contactAddedCount > 0) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Contacts Added Successfully",
|
||||
text: `${contactAddedCount} member${contactAddedCount === 1 ? "" : "s"} added as contact${contactAddedCount === 1 ? "" : "s"}.`,
|
||||
},
|
||||
5000,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.close(notSelectedMembers.map((member) => member.did));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error admitting members:", error);
|
||||
console.error(
|
||||
`Error ${this.isOrganizer ? "admitting members" : "adding contacts"}:`,
|
||||
error,
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "Failed to admit some members. Please try again.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async addContactWithVisibility() {
|
||||
try {
|
||||
const selectedMembers = this.membersData.filter((member) =>
|
||||
this.selectedMembers.includes(member.did),
|
||||
);
|
||||
const notSelectedMembers = this.membersData.filter(
|
||||
(member) => !this.selectedMembers.includes(member.did),
|
||||
);
|
||||
|
||||
let contactsAddedCount = 0;
|
||||
|
||||
for (const member of selectedMembers) {
|
||||
try {
|
||||
// If they're not a contact yet, add them as a contact first
|
||||
if (!member.isContact) {
|
||||
await this.addAsContact(member);
|
||||
contactsAddedCount++;
|
||||
}
|
||||
|
||||
// Set their seesMe to true
|
||||
await this.updateContactVisibility(member.did, true);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Error processing member ${member.did}:`, error);
|
||||
// Continue with other members even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
// Show success notification
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Contacts Added Successfully",
|
||||
text: `${contactsAddedCount} member${contactsAddedCount === 1 ? "" : "s"} added as contact${contactsAddedCount === 1 ? "" : "s"}.`,
|
||||
},
|
||||
5000,
|
||||
);
|
||||
|
||||
this.close(notSelectedMembers.map((member) => member.did));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error adding contacts:", error);
|
||||
this.$notify(
|
||||
{
|
||||
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 +371,39 @@ export default class BulkMembersDialog extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
async addAsContact(member: { did: string; name: string }) {
|
||||
async registerMember(member: MemberData) {
|
||||
try {
|
||||
const newContact = {
|
||||
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: Contact = {
|
||||
did: member.did,
|
||||
name: member.name,
|
||||
registered: isRegistered,
|
||||
};
|
||||
|
||||
await this.$insertContact(newContact);
|
||||
@@ -440,10 +446,10 @@ export default class BulkMembersDialog extends Vue {
|
||||
}
|
||||
|
||||
showContactInfo() {
|
||||
const message =
|
||||
this.dialogType === "admit"
|
||||
? "This user is already your contact, but they are not yet admitted to the meeting."
|
||||
: "This user is already your contact, but your activities are not visible to them yet.";
|
||||
// isOrganizer: true = admit mode, false = visibility mode
|
||||
const message = this.isOrganizer
|
||||
? "This user is already your contact, but they are not yet admitted to the meeting."
|
||||
: "This user is already your contact, but your activities are not visible to them yet.";
|
||||
|
||||
this.$notify(
|
||||
{
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<font-awesome
|
||||
v-if="member.did === activeDid"
|
||||
icon="hand"
|
||||
class="fa-fw text-blue-500"
|
||||
class="fa-fw text-slate-500"
|
||||
/>
|
||||
<font-awesome
|
||||
v-if="
|
||||
@@ -113,10 +113,10 @@
|
||||
</h3>
|
||||
<div
|
||||
v-if="!getContactFor(member.did) && member.did !== activeDid"
|
||||
class="flex items-center gap-1.5 ms-1"
|
||||
class="flex items-center gap-1.5 ml-2 ms-1"
|
||||
>
|
||||
<button
|
||||
class="btn-add-contact"
|
||||
class="btn-add-contact ml-2"
|
||||
title="Add as contact"
|
||||
@click="addAsContact(member)"
|
||||
>
|
||||
@@ -124,7 +124,7 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn-info-contact"
|
||||
class="btn-info-contact ml-2"
|
||||
title="Contact Info"
|
||||
@click="
|
||||
informAboutAddingContact(
|
||||
@@ -135,6 +135,27 @@
|
||||
<font-awesome icon="circle-info" />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="getContactFor(member.did) && member.did !== activeDid"
|
||||
class="flex items-center gap-1.5 ms-1"
|
||||
>
|
||||
<router-link
|
||||
:to="{ name: 'contact-edit', params: { did: member.did } }"
|
||||
>
|
||||
<font-awesome
|
||||
icon="pen"
|
||||
class="text-sm text-blue-500 ml-2 mb-1"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{ name: 'did', params: { did: member.did } }"
|
||||
>
|
||||
<font-awesome
|
||||
icon="arrow-up-right-from-square"
|
||||
class="text-sm text-blue-500 ml-2 mb-1"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
v-if="
|
||||
@@ -202,7 +223,6 @@
|
||||
ref="bulkMembersDialog"
|
||||
:active-did="activeDid"
|
||||
:api-server="apiServer"
|
||||
:dialog-type="isOrganizer ? 'admit' : 'visibility'"
|
||||
:is-organizer="isOrganizer"
|
||||
@close="closeBulkMembersDialogCallback"
|
||||
/>
|
||||
|
||||
@@ -510,14 +510,6 @@ export const NOTIFY_REGISTER_CONTACT = {
|
||||
text: "Do you want to register them?",
|
||||
};
|
||||
|
||||
// Used in: ContactsView.vue (showOnboardMeetingDialog method - complex modal for onboarding meeting)
|
||||
export const NOTIFY_ONBOARDING_MEETING = {
|
||||
title: "Onboarding Meeting",
|
||||
text: "Would you like to start a new meeting?",
|
||||
yesText: "Start New Meeting",
|
||||
noText: "Join Existing Meeting",
|
||||
};
|
||||
|
||||
// TestView.vue specific constants
|
||||
// Used in: TestView.vue (executeSql method - SQL error handling)
|
||||
export const NOTIFY_SQL_ERROR = {
|
||||
|
||||
@@ -70,15 +70,6 @@ export interface AxiosErrorResponse {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
did: string;
|
||||
name: string;
|
||||
publicEncKey: string;
|
||||
registered: boolean;
|
||||
profileImageUrl?: string;
|
||||
nextPublicEncKeyHash?: string;
|
||||
}
|
||||
|
||||
export interface CreateAndSubmitClaimResult {
|
||||
success: boolean;
|
||||
embeddedRecordError?: string;
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from "./common";
|
||||
export * from "./deepLinks";
|
||||
export * from "./limits";
|
||||
export * from "./records";
|
||||
export * from "./user";
|
||||
|
||||
@@ -42,9 +42,6 @@ import {
|
||||
PlanActionClaim,
|
||||
RegisterActionClaim,
|
||||
TenureClaim,
|
||||
} from "../interfaces/claims";
|
||||
|
||||
import {
|
||||
GenericCredWrapper,
|
||||
GenericVerifiableCredential,
|
||||
AxiosErrorResponse,
|
||||
@@ -55,13 +52,11 @@ import {
|
||||
QuantitativeValue,
|
||||
KeyMetaWithPrivate,
|
||||
KeyMetaMaybeWithPrivate,
|
||||
} from "../interfaces/common";
|
||||
import {
|
||||
OfferSummaryRecord,
|
||||
OfferToPlanSummaryRecord,
|
||||
PlanSummaryAndPreviousClaim,
|
||||
PlanSummaryRecord,
|
||||
} from "../interfaces/records";
|
||||
} from "../interfaces";
|
||||
import { logger, safeStringify } from "../utils/logger";
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import { APP_SERVER } from "@/constants/app";
|
||||
@@ -1662,30 +1657,35 @@ export async function register(
|
||||
message?: string;
|
||||
}>(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." };
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -12,20 +12,20 @@
|
||||
</h1>
|
||||
|
||||
<!-- Back -->
|
||||
<router-link
|
||||
<button
|
||||
class="order-first text-lg text-center leading-none p-1"
|
||||
:to="{ name: 'contacts' }"
|
||||
@click="goBack()"
|
||||
>
|
||||
<font-awesome icon="chevron-left" class="block text-center w-[1em]" />
|
||||
</router-link>
|
||||
</button>
|
||||
|
||||
<!-- Help button -->
|
||||
<router-link
|
||||
:to="{ name: 'help' }"
|
||||
<button
|
||||
class="block ms-auto text-sm text-center text-white bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-1.5 rounded-full"
|
||||
@click="goToHelp()"
|
||||
>
|
||||
<font-awesome icon="question" class="block text-center w-[1em]" />
|
||||
</router-link>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Identity Details -->
|
||||
@@ -476,7 +476,7 @@ export default class DIDView extends Vue {
|
||||
* Navigation helper methods
|
||||
*/
|
||||
goBack() {
|
||||
this.$router.go(-1);
|
||||
this.$router.back();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user