You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							459 lines
						
					
					
						
							14 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							459 lines
						
					
					
						
							14 KiB
						
					
					
				
								<template>
							 | 
						|
								  <div v-if="visible" class="dialog-overlay">
							 | 
						|
								    <div class="dialog">
							 | 
						|
								      <div class="text-slate-900 text-center">
							 | 
						|
								        <h3 class="text-lg font-semibold leading-[1.25] mb-2">
							 | 
						|
								          {{ title }}
							 | 
						|
								        </h3>
							 | 
						|
								        <p class="text-sm mb-4">
							 | 
						|
								          {{ description }}
							 | 
						|
								        </p>
							 | 
						|
								
							 | 
						|
								        <!-- Member Selection Table -->
							 | 
						|
								        <div class="mb-4">
							 | 
						|
								          <table
							 | 
						|
								            class="w-full border-collapse border border-slate-300 text-sm text-start"
							 | 
						|
								          >
							 | 
						|
								            <!-- Select All Header -->
							 | 
						|
								            <thead v-if="membersData && membersData.length > 0">
							 | 
						|
								              <tr class="bg-slate-100 font-medium">
							 | 
						|
								                <th class="border border-slate-300 px-3 py-2">
							 | 
						|
								                  <label class="flex items-center gap-2">
							 | 
						|
								                    <input
							 | 
						|
								                      type="checkbox"
							 | 
						|
								                      :checked="isAllSelected"
							 | 
						|
								                      :indeterminate="isIndeterminate"
							 | 
						|
								                      @change="toggleSelectAll"
							 | 
						|
								                    />
							 | 
						|
								                    Select All
							 | 
						|
								                  </label>
							 | 
						|
								                </th>
							 | 
						|
								              </tr>
							 | 
						|
								            </thead>
							 | 
						|
								            <tbody>
							 | 
						|
								              <!-- Empty State -->
							 | 
						|
								              <tr v-if="!membersData || membersData.length === 0">
							 | 
						|
								                <td
							 | 
						|
								                  class="border border-slate-300 px-3 py-2 text-center italic text-gray-500"
							 | 
						|
								                >
							 | 
						|
								                  {{ emptyStateText }}
							 | 
						|
								                </td>
							 | 
						|
								              </tr>
							 | 
						|
								              <!-- Member Rows -->
							 | 
						|
								              <tr
							 | 
						|
								                v-for="member in membersData || []"
							 | 
						|
								                :key="member.member.memberId"
							 | 
						|
								              >
							 | 
						|
								                <td class="border border-slate-300 px-3 py-2">
							 | 
						|
								                  <div class="flex items-center justify-between gap-2">
							 | 
						|
								                    <label class="flex items-center gap-2">
							 | 
						|
								                      <input
							 | 
						|
								                        type="checkbox"
							 | 
						|
								                        :checked="isMemberSelected(member.did)"
							 | 
						|
								                        @change="toggleMemberSelection(member.did)"
							 | 
						|
								                      />
							 | 
						|
								                      <div class="">
							 | 
						|
								                        <div class="text-sm font-semibold">
							 | 
						|
								                          {{ member.name || SOMEONE_UNNAMED }}
							 | 
						|
								                        </div>
							 | 
						|
								                        <div
							 | 
						|
								                          class="flex items-center gap-0.5 text-xs text-slate-500"
							 | 
						|
								                        >
							 | 
						|
								                          <span class="font-semibold sm:hidden">DID:</span>
							 | 
						|
								                          <span
							 | 
						|
								                            class="w-[35vw] sm:w-auto truncate text-left"
							 | 
						|
								                            style="direction: rtl"
							 | 
						|
								                            >{{ member.did }}</span
							 | 
						|
								                          >
							 | 
						|
								                        </div>
							 | 
						|
								                      </div>
							 | 
						|
								                    </label>
							 | 
						|
								
							 | 
						|
								                    <!-- Contact indicator - only show if they are already a contact -->
							 | 
						|
								                    <font-awesome
							 | 
						|
								                      v-if="member.isContact"
							 | 
						|
								                      icon="user-circle"
							 | 
						|
								                      class="fa-fw ms-auto text-slate-400 cursor-pointer hover:text-slate-600"
							 | 
						|
								                      @click="showContactInfo"
							 | 
						|
								                    />
							 | 
						|
								                  </div>
							 | 
						|
								                </td>
							 | 
						|
								              </tr>
							 | 
						|
								            </tbody>
							 | 
						|
								            <!-- Select All Footer -->
							 | 
						|
								            <tfoot v-if="membersData && membersData.length > 0">
							 | 
						|
								              <tr class="bg-slate-100 font-medium">
							 | 
						|
								                <th class="border border-slate-300 px-3 py-2">
							 | 
						|
								                  <label class="flex items-center gap-2">
							 | 
						|
								                    <input
							 | 
						|
								                      type="checkbox"
							 | 
						|
								                      :checked="isAllSelected"
							 | 
						|
								                      :indeterminate="isIndeterminate"
							 | 
						|
								                      @change="toggleSelectAll"
							 | 
						|
								                    />
							 | 
						|
								                    Select All
							 | 
						|
								                  </label>
							 | 
						|
								                </th>
							 | 
						|
								              </tr>
							 | 
						|
								            </tfoot>
							 | 
						|
								          </table>
							 | 
						|
								        </div>
							 | 
						|
								
							 | 
						|
								        <!-- Action Buttons -->
							 | 
						|
								        <div class="space-y-2">
							 | 
						|
								          <!-- Main Action Button -->
							 | 
						|
								          <button
							 | 
						|
								            v-if="membersData && membersData.length > 0"
							 | 
						|
								            :disabled="!hasSelectedMembers"
							 | 
						|
								            :class="[
							 | 
						|
								              'block w-full text-center text-md font-bold uppercase px-2 py-2 rounded-md',
							 | 
						|
								              hasSelectedMembers
							 | 
						|
								                ? 'bg-blue-600 text-white cursor-pointer'
							 | 
						|
								                : 'bg-slate-400 text-slate-200 cursor-not-allowed',
							 | 
						|
								            ]"
							 | 
						|
								            @click="handleMainAction"
							 | 
						|
								          >
							 | 
						|
								            {{ buttonText }}
							 | 
						|
								          </button>
							 | 
						|
								          <!-- Cancel Button -->
							 | 
						|
								          <button
							 | 
						|
								            class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
							 | 
						|
								            @click="cancel"
							 | 
						|
								          >
							 | 
						|
								            Maybe Later
							 | 
						|
								          </button>
							 | 
						|
								        </div>
							 | 
						|
								      </div>
							 | 
						|
								    </div>
							 | 
						|
								  </div>
							 | 
						|
								</template>
							 | 
						|
								
							 | 
						|
								<script lang="ts">
							 | 
						|
								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 { createNotifyHelpers } from "@/utils/notify";
							 | 
						|
								
							 | 
						|
								@Component({
							 | 
						|
								  mixins: [PlatformServiceMixin],
							 | 
						|
								  emits: ["close"],
							 | 
						|
								})
							 | 
						|
								export default class BulkMembersDialog extends Vue {
							 | 
						|
								  @Prop({ default: "" }) activeDid!: string;
							 | 
						|
								  @Prop({ default: "" }) apiServer!: string;
							 | 
						|
								  @Prop({ required: true }) dialogType!: "admit" | "visibility";
							 | 
						|
								  @Prop({ required: true }) isOrganizer!: boolean;
							 | 
						|
								
							 | 
						|
								  // Vue notification system
							 | 
						|
								  $notify!: (
							 | 
						|
								    notification: { group: string; type: string; title: string; text: string },
							 | 
						|
								    timeout?: number,
							 | 
						|
								  ) => void;
							 | 
						|
								
							 | 
						|
								  // Notification system
							 | 
						|
								  notify!: ReturnType<typeof createNotifyHelpers>;
							 | 
						|
								
							 | 
						|
								  // Component state
							 | 
						|
								  membersData: MemberData[] = [];
							 | 
						|
								  selectedMembers: string[] = [];
							 | 
						|
								  visible = false;
							 | 
						|
								
							 | 
						|
								  // Constants
							 | 
						|
								  // In Vue templates, imported constants need to be explicitly made available to the template
							 | 
						|
								  readonly SOMEONE_UNNAMED = SOMEONE_UNNAMED;
							 | 
						|
								
							 | 
						|
								  get hasSelectedMembers() {
							 | 
						|
								    return this.selectedMembers.length > 0;
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  get isAllSelected() {
							 | 
						|
								    if (!this.membersData || this.membersData.length === 0) return false;
							 | 
						|
								    return this.membersData.every((member) =>
							 | 
						|
								      this.selectedMembers.includes(member.did),
							 | 
						|
								    );
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  get isIndeterminate() {
							 | 
						|
								    if (!this.membersData || this.membersData.length === 0) return false;
							 | 
						|
								    const selectedCount = this.membersData.filter((member) =>
							 | 
						|
								      this.selectedMembers.includes(member.did),
							 | 
						|
								    ).length;
							 | 
						|
								    return selectedCount > 0 && selectedCount < this.membersData.length;
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  get title() {
							 | 
						|
								    return this.isOrganizer
							 | 
						|
								      ? "Admit Pending Members"
							 | 
						|
								      : "Add Members to Contacts";
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  get description() {
							 | 
						|
								    return this.isOrganizer
							 | 
						|
								      ? "Would you like to admit these members to the meeting and add them to your contacts?"
							 | 
						|
								      : "Would you like to add these members to your contacts?";
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  get buttonText() {
							 | 
						|
								    return this.isOrganizer ? "Admit + Add to Contacts" : "Add to Contacts";
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  get emptyStateText() {
							 | 
						|
								    return this.isOrganizer
							 | 
						|
								      ? "No pending members to admit"
							 | 
						|
								      : "No members are not in your contacts";
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  created() {
							 | 
						|
								    this.notify = createNotifyHelpers(this.$notify);
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  open(members: MemberData[]) {
							 | 
						|
								    this.visible = true;
							 | 
						|
								    this.membersData = members;
							 | 
						|
								    // Select all by default
							 | 
						|
								    this.selectedMembers = this.membersData.map((member) => member.did);
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  close(notSelectedMemberDids: string[]) {
							 | 
						|
								    this.visible = false;
							 | 
						|
								    this.$emit("close", { notSelectedMemberDids: notSelectedMemberDids });
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  cancel() {
							 | 
						|
								    this.close(this.membersData.map((member) => member.did));
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  toggleSelectAll() {
							 | 
						|
								    if (!this.membersData || this.membersData.length === 0) return;
							 | 
						|
								
							 | 
						|
								    if (this.isAllSelected) {
							 | 
						|
								      // Deselect all
							 | 
						|
								      this.selectedMembers = [];
							 | 
						|
								    } else {
							 | 
						|
								      // Select all
							 | 
						|
								      this.selectedMembers = this.membersData.map((member) => member.did);
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  toggleMemberSelection(memberDid: string) {
							 | 
						|
								    const index = this.selectedMembers.indexOf(memberDid);
							 | 
						|
								    if (index > -1) {
							 | 
						|
								      this.selectedMembers.splice(index, 1);
							 | 
						|
								    } else {
							 | 
						|
								      this.selectedMembers.push(memberDid);
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  isMemberSelected(memberDid: string) {
							 | 
						|
								    return this.selectedMembers.includes(memberDid);
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  async handleMainAction() {
							 | 
						|
								    if (this.dialogType === "admit") {
							 | 
						|
								      await this.admitWithVisibility();
							 | 
						|
								    } else {
							 | 
						|
								      await this.addContactWithVisibility();
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  async admitWithVisibility() {
							 | 
						|
								    try {
							 | 
						|
								      const selectedMembers = this.membersData.filter((member) =>
							 | 
						|
								        this.selectedMembers.includes(member.did),
							 | 
						|
								      );
							 | 
						|
								      const notSelectedMembers = this.membersData.filter(
							 | 
						|
								        (member) => !this.selectedMembers.includes(member.did),
							 | 
						|
								      );
							 | 
						|
								
							 | 
						|
								      let admittedCount = 0;
							 | 
						|
								      let contactAddedCount = 0;
							 | 
						|
								
							 | 
						|
								      for (const member of selectedMembers) {
							 | 
						|
								        try {
							 | 
						|
								          // First, admit the member
							 | 
						|
								          await this.admitMember(member);
							 | 
						|
								          admittedCount++;
							 | 
						|
								
							 | 
						|
								          // If they're not a contact yet, add them as a contact
							 | 
						|
								          if (!member.isContact) {
							 | 
						|
								            await this.addAsContact(member);
							 | 
						|
								            contactAddedCount++;
							 | 
						|
								          }
							 | 
						|
								
							 | 
						|
								          // 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: "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,
							 | 
						|
								      );
							 | 
						|
								
							 | 
						|
								      this.close(notSelectedMembers.map((member) => member.did));
							 | 
						|
								    } catch (error) {
							 | 
						|
								      // eslint-disable-next-line no-console
							 | 
						|
								      console.error("Error admitting members:", 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.",
							 | 
						|
								        },
							 | 
						|
								        5000,
							 | 
						|
								      );
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  async admitMember(member: {
							 | 
						|
								    did: string;
							 | 
						|
								    name: string;
							 | 
						|
								    member: { memberId: string };
							 | 
						|
								  }) {
							 | 
						|
								    try {
							 | 
						|
								      const headers = await getHeaders(this.activeDid);
							 | 
						|
								      await this.axios.put(
							 | 
						|
								        `${this.apiServer}/api/partner/groupOnboardMember/${member.member.memberId}`,
							 | 
						|
								        { admitted: true },
							 | 
						|
								        { headers },
							 | 
						|
								      );
							 | 
						|
								    } catch (err) {
							 | 
						|
								      // eslint-disable-next-line no-console
							 | 
						|
								      console.error("Error admitting member:", err);
							 | 
						|
								      throw err;
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  async addAsContact(member: { did: string; name: string }) {
							 | 
						|
								    try {
							 | 
						|
								      const newContact = {
							 | 
						|
								        did: member.did,
							 | 
						|
								        name: member.name,
							 | 
						|
								      };
							 | 
						|
								
							 | 
						|
								      await this.$insertContact(newContact);
							 | 
						|
								    } catch (err) {
							 | 
						|
								      // eslint-disable-next-line no-console
							 | 
						|
								      console.error("Error adding contact:", err);
							 | 
						|
								      if (err instanceof Error && err.message?.indexOf("already exists") > -1) {
							 | 
						|
								        // Contact already exists, continue
							 | 
						|
								      } else {
							 | 
						|
								        throw err; // Re-throw if it's not a duplicate error
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  async updateContactVisibility(did: string, seesMe: boolean) {
							 | 
						|
								    try {
							 | 
						|
								      // Get the contact object
							 | 
						|
								      const contact = await this.$getContact(did);
							 | 
						|
								      if (!contact) {
							 | 
						|
								        throw new Error(`Contact not found for DID: ${did}`);
							 | 
						|
								      }
							 | 
						|
								
							 | 
						|
								      // Use the proper API to set visibility on the server
							 | 
						|
								      const result = await setVisibilityUtil(
							 | 
						|
								        this.activeDid,
							 | 
						|
								        this.apiServer,
							 | 
						|
								        this.axios,
							 | 
						|
								        contact,
							 | 
						|
								        seesMe,
							 | 
						|
								      );
							 | 
						|
								
							 | 
						|
								      if (!result.success) {
							 | 
						|
								        throw new Error(result.error || "Failed to set visibility");
							 | 
						|
								      }
							 | 
						|
								    } catch (err) {
							 | 
						|
								      // eslint-disable-next-line no-console
							 | 
						|
								      console.error("Error updating contact visibility:", err);
							 | 
						|
								      throw err;
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  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.";
							 | 
						|
								
							 | 
						|
								    this.$notify(
							 | 
						|
								      {
							 | 
						|
								        group: "alert",
							 | 
						|
								        type: "info",
							 | 
						|
								        title: "Contact Info",
							 | 
						|
								        text: message,
							 | 
						|
								      },
							 | 
						|
								      5000,
							 | 
						|
								    );
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								</script>
							 | 
						|
								
							 |