From e9ea89edae7bb2f35a5080865d078b006610e49b Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Tue, 21 Oct 2025 18:13:10 +0800 Subject: [PATCH 01/26] feat: enhance members list UI with visual indicators and improved styling - Sort members list with organizer first, then non-admitted, then admitted - Add crown icon for meeting organizer identification - Add spinner icon for non-admitted members - Implement conditional styling for non-admitted members - Update button styling to use circle icons instead of rounded backgrounds - Improve visual hierarchy with better spacing and color coding --- src/components/MembersList.vue | 114 +++++++++++++++++++++++---------- src/libs/fontawesome.ts | 4 ++ 2 files changed, 84 insertions(+), 34 deletions(-) diff --git a/src/components/MembersList.vue b/src/components/MembersList.vue index e26613bf..fe54b8d1 100644 --- a/src/components/MembersList.vue +++ b/src/components/MembersList.vue @@ -28,26 +28,14 @@ v-if="membersToShow().length > 0 && showOrganizerTools && isOrganizer" > Click - - - + / - - - + to add/remove them to/from the meeting.
  • Click - - - + to add them to your contacts.
  • @@ -74,16 +62,38 @@
  • -

    +

    + + {{ member.name || unnamedMember }}

    @@ -110,17 +120,23 @@ v-if=" showOrganizerTools && isOrganizer && member.did !== activeDid " - class="flex items-center gap-1" + class="flex items-center gap-1.5" > @@ -129,7 +145,7 @@ title="Admission Info" @click="informAboutAdmission()" > - +
    @@ -378,17 +394,44 @@ export default class MembersList extends Vue { } membersToShow(): DecryptedMember[] { + let members: DecryptedMember[] = []; + if (this.isOrganizer) { if (this.showOrganizerTools) { - return this.decryptedMembers; + members = this.decryptedMembers; } else { - return this.decryptedMembers.filter( + members = this.decryptedMembers.filter( (member: DecryptedMember) => member.member.admitted, ); } + } else { + // non-organizers only get visible members from server + members = this.decryptedMembers; } - // non-organizers only get visible members from server - return this.decryptedMembers; + + // Sort members according to priority: + // 1. Organizer at the top + // 2. Non-admitted members next + // 3. Everyone else after + return members.sort((a, b) => { + // Check if either member is the organizer (first member in original list) + const aIsOrganizer = a.member.memberId === this.members[0]?.memberId; + const bIsOrganizer = b.member.memberId === this.members[0]?.memberId; + + // Organizer always comes first + if (aIsOrganizer && !bIsOrganizer) return -1; + if (!aIsOrganizer && bIsOrganizer) return 1; + + // If both are organizers or neither are organizers, sort by admission status + if (aIsOrganizer && bIsOrganizer) return 0; // Both organizers, maintain original order + + // Non-admitted members come before admitted members + if (!a.member.admitted && b.member.admitted) return -1; + if (a.member.admitted && !b.member.admitted) return 1; + + // If admission status is the same, maintain original order + return 0; + }); } informAboutAdmission() { @@ -718,23 +761,26 @@ export default class MembersList extends Vue { .btn-add-contact { /* stylelint-disable-next-line at-rule-no-unknown */ - @apply w-6 h-6 flex items-center justify-center rounded-full - bg-green-100 text-green-600 hover:bg-green-200 hover:text-green-800 + @apply text-lg text-green-600 hover:text-green-800 transition-colors; } .btn-info-contact, .btn-info-admission { /* stylelint-disable-next-line at-rule-no-unknown */ - @apply w-6 h-6 flex items-center justify-center rounded-full - bg-slate-100 text-slate-400 hover:text-slate-600 + @apply text-slate-400 hover:text-slate-600 transition-colors; } -.btn-admission { +.btn-admission-add { /* stylelint-disable-next-line at-rule-no-unknown */ - @apply w-6 h-6 flex items-center justify-center rounded-full - bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 + @apply text-lg text-blue-500 hover:text-blue-700 + transition-colors; +} + +.btn-admission-remove { + /* stylelint-disable-next-line at-rule-no-unknown */ + @apply text-lg text-rose-500 hover:text-rose-700 transition-colors; } diff --git a/src/libs/fontawesome.ts b/src/libs/fontawesome.ts index efd8ff03..b2e1ad13 100644 --- a/src/libs/fontawesome.ts +++ b/src/libs/fontawesome.ts @@ -29,6 +29,7 @@ import { faCircle, faCircleCheck, faCircleInfo, + faCircleMinus, faCirclePlus, faCircleQuestion, faCircleRight, @@ -37,6 +38,7 @@ import { faCoins, faComment, faCopy, + faCrown, faDollar, faDownload, faEllipsis, @@ -123,6 +125,7 @@ library.add( faCircle, faCircleCheck, faCircleInfo, + faCircleMinus, faCirclePlus, faCircleQuestion, faCircleRight, @@ -131,6 +134,7 @@ library.add( faCoins, faComment, faCopy, + faCrown, faDollar, faDownload, faEllipsis, From 035509224b46badc62ff05a3b6dc50012051e912 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Tue, 21 Oct 2025 22:00:21 +0800 Subject: [PATCH 02/26] feat: change icon for pending members - Changed from an animating spinner to a static hourglass --- src/components/MembersList.vue | 4 ++-- src/libs/fontawesome.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/MembersList.vue b/src/components/MembersList.vue index fe54b8d1..1a5babbc 100644 --- a/src/components/MembersList.vue +++ b/src/components/MembersList.vue @@ -86,8 +86,8 @@ /> {{ member.name || unnamedMember }} diff --git a/src/libs/fontawesome.ts b/src/libs/fontawesome.ts index b2e1ad13..947833e6 100644 --- a/src/libs/fontawesome.ts +++ b/src/libs/fontawesome.ts @@ -60,6 +60,7 @@ import { faHand, faHandHoldingDollar, faHandHoldingHeart, + faHourglassHalf, faHouseChimney, faImage, faImagePortrait, @@ -156,6 +157,7 @@ library.add( faHand, faHandHoldingDollar, faHandHoldingHeart, + faHourglassHalf, faHouseChimney, faImage, faImagePortrait, From 6fbc9c2a5b772138a3af04965035246ba569cfa8 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Wed, 22 Oct 2025 21:56:00 +0800 Subject: [PATCH 03/26] feat: Add AdmitPendingMembersDialog for bulk member admission - Add new AdmitPendingMembersDialog component with checkbox selection - Support two action modes: "Admit + Add Contacts" and "Admit Only" - Integrate dialog into MembersList with proper sequencing - Show admit dialog before visibility dialog when pending members exist - Fix auto-refresh pause/resume logic for both dialogs - Ensure consistent dialog behavior between initial load and manual refresh - Add proper async/await handling for data refresh operations - Optimize dialog state management and remove redundant code - Maintain proper flag timing to prevent race conditions The admit dialog now shows automatically when there are pending members, allowing organizers to efficiently admit multiple members at once while optionally adding them as contacts and setting visibility preferences. --- src/components/AdmitPendingMembersDialog.vue | 458 +++++++++++++++++++ src/components/MembersList.vue | 175 ++++++- 2 files changed, 620 insertions(+), 13 deletions(-) create mode 100644 src/components/AdmitPendingMembersDialog.vue diff --git a/src/components/AdmitPendingMembersDialog.vue b/src/components/AdmitPendingMembersDialog.vue new file mode 100644 index 00000000..d6cbd013 --- /dev/null +++ b/src/components/AdmitPendingMembersDialog.vue @@ -0,0 +1,458 @@ + + + + + diff --git a/src/components/MembersList.vue b/src/components/MembersList.vue index 1a5babbc..9b082312 100644 --- a/src/components/MembersList.vue +++ b/src/components/MembersList.vue @@ -177,6 +177,16 @@ + + + = []; + admitDialogDismissed = false; + isManualRefresh = false; + // Set Visibility Dialog state showSetVisibilityDialog = false; visibilityDialogMembers: Array<{ @@ -296,8 +319,13 @@ export default class MembersList extends Vue { // Start auto-refresh this.startAutoRefresh(); - // Check if we should show the visibility dialog on initial load - this.checkAndShowVisibilityDialog(); + // Check if we should show the admit pending members dialog first + this.checkAndShowAdmitPendingDialog(); + + // If no pending members, check for visibility dialog + if (!this.showAdmitPendingDialog) { + this.checkAndShowVisibilityDialog(); + } } async refreshData() { @@ -305,8 +333,13 @@ export default class MembersList extends Vue { await this.loadContacts(); await this.fetchMembers(); - // Check if we should show the visibility dialog after refresh - this.checkAndShowVisibilityDialog(); + // Check if we should show the admit pending members dialog first + this.checkAndShowAdmitPendingDialog(); + + // If no pending members, check for visibility dialog + if (!this.showAdmitPendingDialog) { + this.checkAndShowVisibilityDialog(); + } } async fetchMembers() { @@ -463,6 +496,26 @@ export default class MembersList extends Vue { return this.contacts.find((contact) => contact.did === did); } + getPendingMembers() { + return this.decryptedMembers + .filter((member) => { + // Exclude the current user + if (member.did === this.activeDid) { + return false; + } + // Only include non-admitted members + return !member.member.admitted; + }) + .map((member) => ({ + did: member.did, + name: member.name, + isContact: !!this.getContactFor(member.did), + member: { + memberId: member.member.memberId.toString(), + }, + })); + } + getMembersForVisibility() { return this.decryptedMembers .filter((member) => { @@ -492,7 +545,8 @@ export default class MembersList extends Vue { * Check if we should show the visibility dialog * Returns true if there are members for visibility and either: * - This is the first time (no previous members tracked), OR - * - New members have been added since last check (not removed) + * - New members have been added since last check (not removed), OR + * - This is a manual refresh (isManualRefresh flag is set) */ shouldShowVisibilityDialog(): boolean { const currentMembers = this.getMembersForVisibility(); @@ -506,6 +560,11 @@ export default class MembersList extends Vue { return true; } + // If this is a manual refresh, always show dialog if there are members + if (this.isManualRefresh) { + return true; + } + // Check if new members have been added (not just any change) const currentMemberIds = currentMembers.map((m) => m.did); const previousMemberIds = this.previousVisibilityMembers; @@ -527,6 +586,31 @@ export default class MembersList extends Vue { this.previousVisibilityMembers = currentMembers.map((m) => m.did); } + /** + * Check if we should show the admit pending members dialog + */ + shouldShowAdmitPendingDialog(): boolean { + // Don't show if already dismissed + if (this.admitDialogDismissed) { + return false; + } + + const pendingMembers = this.getPendingMembers(); + return pendingMembers.length > 0; + } + + /** + * Show the admit pending members dialog if conditions are met + */ + checkAndShowAdmitPendingDialog() { + if (this.shouldShowAdmitPendingDialog()) { + this.showAdmitPendingDialogMethod(); + } else { + // Ensure dialog state is false when no pending members + this.showAdmitPendingDialog = false; + } + } + /** * Show the visibility dialog if conditions are met */ @@ -675,6 +759,24 @@ export default class MembersList extends Vue { } } + showAdmitPendingDialogMethod() { + // Filter members to show only pending (non-admitted) members + const pendingMembers = this.getPendingMembers(); + + // Only show dialog if there are pending members + if (pendingMembers.length === 0) { + this.showAdmitPendingDialog = false; + return; + } + + // Pause auto-refresh when dialog opens + this.stopAutoRefresh(); + + // Open the dialog directly + this.pendingMembersData = pendingMembers; + this.showAdmitPendingDialog = true; + } + showSetBulkVisibilityDialog() { // Filter members to show only those who need visibility set const membersForVisibility = this.getMembersForVisibility(); @@ -682,6 +784,9 @@ export default class MembersList extends Vue { // Pause auto-refresh when dialog opens this.stopAutoRefresh(); + // Reset manual refresh flag when showing visibility dialog + this.isManualRefresh = false; + // Open the dialog directly this.visibilityDialogMembers = membersForVisibility; this.showSetVisibilityDialog = true; @@ -717,28 +822,72 @@ export default class MembersList extends Vue { } } - manualRefresh() { + async manualRefresh() { // Clear existing auto-refresh interval if (this.autoRefreshInterval) { clearInterval(this.autoRefreshInterval); this.autoRefreshInterval = null; } - // Trigger immediate refresh and restart timer - this.refreshData(); - this.startAutoRefresh(); + // Set manual refresh flag + this.isManualRefresh = true; + // Reset the dismissed flag on manual refresh + this.admitDialogDismissed = false; - // Always show dialog on manual refresh if there are members for visibility - if (this.getMembersForVisibility().length > 0) { - this.showSetBulkVisibilityDialog(); + // Trigger immediate refresh + await this.refreshData(); + + // Only start auto-refresh if no dialogs are showing + if (!this.showAdmitPendingDialog && !this.showSetVisibilityDialog) { + this.startAutoRefresh(); } } + // Admit Pending Members Dialog methods + async closeAdmitPendingDialog() { + this.showAdmitPendingDialog = false; + this.pendingMembersData = []; + this.admitDialogDismissed = true; + + // Handle manual refresh flow + if (this.isManualRefresh) { + await this.handleManualRefreshFlow(); + this.isManualRefresh = false; + } else { + // Normal flow: refresh data and resume auto-refresh + this.refreshData(); + this.startAutoRefresh(); + } + } + + async handleManualRefreshFlow() { + // Refresh data to reflect any changes made in the admit dialog + await this.refreshData(); + + // Use the same logic as normal flow to check for visibility dialog + this.checkAndShowVisibilityDialog(); + + // If no visibility dialog was shown, resume auto-refresh + if (!this.showSetVisibilityDialog) { + this.startAutoRefresh(); + } + } + + async onAdmitPendingSuccess(_result: { + admittedCount: number; + contactAddedCount: number; + visibilitySetCount: number; + }) { + // After admitting pending members, close the admit dialog + // The visibility dialog will be handled by the closeAdmitPendingDialog flow + await this.closeAdmitPendingDialog(); + } + // Set Visibility Dialog methods closeSetVisibilityDialog() { this.showSetVisibilityDialog = false; this.visibilityDialogMembers = []; - // Refresh data when dialog is closed + // Refresh data when dialog is closed to reflect any changes made this.refreshData(); // Resume auto-refresh when dialog is closed this.startAutoRefresh(); From ad51c187aa6e1e41522187c2e26462f79094c1e8 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Thu, 23 Oct 2025 19:59:55 +0800 Subject: [PATCH 04/26] Update AdmitPendingMembersDialog.vue feat: add DID display to Pending Members dialog - Restructure member display with better visual hierarchy - Add DID display with responsive truncation for mobile - Simplify button labels ("Admit + Add Contacts" and "Admit Only") --- src/components/AdmitPendingMembersDialog.vue | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/AdmitPendingMembersDialog.vue b/src/components/AdmitPendingMembersDialog.vue index d6cbd013..ca668cbd 100644 --- a/src/components/AdmitPendingMembersDialog.vue +++ b/src/components/AdmitPendingMembersDialog.vue @@ -52,7 +52,21 @@ :checked="isMemberSelected(member.did)" @change="toggleMemberSelection(member.did)" /> - {{ member.name || SOMEONE_UNNAMED }} +
    +
    + {{ member.name || SOMEONE_UNNAMED }} +
    +
    + DID: + {{ member.did }} +
    +
    @@ -81,7 +95,7 @@ ]" @click="admitAndSetVisibility" > - Admit Pending + Add Contacts + Admit + Add Contacts +