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.
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