Browse Source
			
			
			
			
				
		- Merge AdmitPendingMembersDialog and SetBulkVisibilityDialog into single BulkMembersDialog - Add dynamic props for dialog type, title, description, button text, and empty state - Support both 'admit' and 'visibility' modes with conditional behavior - Rename setVisibilityForSelectedMembers to addContactWithVisibility for clarity - Update success counting to track contacts added vs visibility set - Improve error messages to reflect primary action of adding contacts - Update MembersList to use unified dialog with role-based configuration - Remove unused libsUtil import from MembersList - Update comments and method names to reflect unified functionality - Rename closeMemberSelectionDialogCallback to closeBulkMembersDialogCallback This consolidation eliminates ~200 lines of duplicate code while maintaining all existing functionality and improving maintainability through a single source of truth for bulk member operations.pull/210/head
				 3 changed files with 114 additions and 368 deletions
			
			
		@ -1,324 +0,0 @@ | 
				
			|||||
<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"> | 
					 | 
				
			||||
          Add Members to Contacts | 
					 | 
				
			||||
        </h3> | 
					 | 
				
			||||
        <p class="text-sm mb-4"> | 
					 | 
				
			||||
          Would you like to add these members to your contacts? | 
					 | 
				
			||||
        </p> | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        <!-- Custom table area - you can customize this --> | 
					 | 
				
			||||
        <div class="mb-4"> | 
					 | 
				
			||||
          <table | 
					 | 
				
			||||
            class="w-full border-collapse border border-slate-300 text-sm text-start" | 
					 | 
				
			||||
          > | 
					 | 
				
			||||
            <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> | 
					 | 
				
			||||
              <!-- Dynamic data from MembersList --> | 
					 | 
				
			||||
              <tr v-if="!membersData || membersData.length === 0"> | 
					 | 
				
			||||
                <td | 
					 | 
				
			||||
                  class="border border-slate-300 px-3 py-2 text-center italic text-gray-500" | 
					 | 
				
			||||
                > | 
					 | 
				
			||||
                  No members are not in your contacts | 
					 | 
				
			||||
                </td> | 
					 | 
				
			||||
              </tr> | 
					 | 
				
			||||
              <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)" | 
					 | 
				
			||||
                      /> | 
					 | 
				
			||||
                      {{ member.name || SOMEONE_UNNAMED }} | 
					 | 
				
			||||
                    </label> | 
					 | 
				
			||||
 | 
					 | 
				
			||||
                    <!-- Friend 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> | 
					 | 
				
			||||
            <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> | 
					 | 
				
			||||
 | 
					 | 
				
			||||
        <div class="space-y-2"> | 
					 | 
				
			||||
          <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="setVisibilityForSelectedMembers" | 
					 | 
				
			||||
          > | 
					 | 
				
			||||
            Add to Contacts | 
					 | 
				
			||||
          </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 } from "@/libs/endorserServer"; | 
					 | 
				
			||||
import { createNotifyHelpers } from "@/utils/notify"; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
@Component({ | 
					 | 
				
			||||
  mixins: [PlatformServiceMixin], | 
					 | 
				
			||||
  emits: ["close"], | 
					 | 
				
			||||
}) | 
					 | 
				
			||||
export default class SetBulkVisibilityDialog extends Vue { | 
					 | 
				
			||||
  @Prop({ default: "" }) activeDid!: string; | 
					 | 
				
			||||
  @Prop({ default: "" }) apiServer!: string; | 
					 | 
				
			||||
 | 
					 | 
				
			||||
  // 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; | 
					 | 
				
			||||
  } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
  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 setVisibilityForSelectedMembers() { | 
					 | 
				
			||||
    try { | 
					 | 
				
			||||
      const selectedMembers = this.membersData.filter((member) => | 
					 | 
				
			||||
        this.selectedMembers.includes(member.did), | 
					 | 
				
			||||
      ); | 
					 | 
				
			||||
      const notSelectedMembers = this.membersData.filter( | 
					 | 
				
			||||
        (member) => !this.selectedMembers.includes(member.did), | 
					 | 
				
			||||
      ); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
      let successCount = 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); | 
					 | 
				
			||||
          } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
          // Set their seesMe to true | 
					 | 
				
			||||
          await this.updateContactVisibility(member.did, true); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
          successCount++; | 
					 | 
				
			||||
        } 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: "Visibility Set Successfully", | 
					 | 
				
			||||
          text: `Visibility set for ${successCount} member${successCount === 1 ? "" : "s"}.`, | 
					 | 
				
			||||
        }, | 
					 | 
				
			||||
        5000, | 
					 | 
				
			||||
      ); | 
					 | 
				
			||||
 | 
					 | 
				
			||||
      this.close(notSelectedMembers.map((member) => member.did)); | 
					 | 
				
			||||
    } catch (error) { | 
					 | 
				
			||||
      // eslint-disable-next-line no-console | 
					 | 
				
			||||
      console.error("Error setting visibility:", error); | 
					 | 
				
			||||
      this.$notify( | 
					 | 
				
			||||
        { | 
					 | 
				
			||||
          group: "alert", | 
					 | 
				
			||||
          type: "danger", | 
					 | 
				
			||||
          title: "Error", | 
					 | 
				
			||||
          text: "Failed to set visibility for some members. Please try again.", | 
					 | 
				
			||||
        }, | 
					 | 
				
			||||
        5000, | 
					 | 
				
			||||
      ); | 
					 | 
				
			||||
    } | 
					 | 
				
			||||
  } | 
					 | 
				
			||||
 | 
					 | 
				
			||||
  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() { | 
					 | 
				
			||||
    this.$notify( | 
					 | 
				
			||||
      { | 
					 | 
				
			||||
        group: "alert", | 
					 | 
				
			||||
        type: "info", | 
					 | 
				
			||||
        title: "Contact Info", | 
					 | 
				
			||||
        text: "This user is already your contact, but your activities are not visible to them yet.", | 
					 | 
				
			||||
      }, | 
					 | 
				
			||||
      5000, | 
					 | 
				
			||||
    ); | 
					 | 
				
			||||
  } | 
					 | 
				
			||||
} | 
					 | 
				
			||||
</script> | 
					 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue