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