diff --git a/.husky/pre-commit b/.husky/pre-commit
index 9d7ede0a..b31dbcc0 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -9,6 +9,10 @@ echo "🔍 Running pre-commit hooks..."
# Run lint-fix first
echo "📝 Running lint-fix..."
+
+# Capture git status before lint-fix to detect changes
+git_status_before=$(git status --porcelain)
+
npm run lint-fix || {
echo
echo "❌ Linting failed. Please fix the issues and try again."
@@ -18,6 +22,36 @@ npm run lint-fix || {
exit 1
}
+# Check if lint-fix made any changes
+git_status_after=$(git status --porcelain)
+
+if [ "$git_status_before" != "$git_status_after" ]; then
+ echo
+ echo "⚠️ lint-fix made changes to your files!"
+ echo "📋 Changes detected:"
+ git diff --name-only
+ echo
+ echo "❓ What would you like to do?"
+ echo " [c] Continue commit without the new changes"
+ echo " [a] Abort commit (recommended - review and stage the changes)"
+ echo
+ printf "Choose [c/a]: "
+ # The `< /dev/tty` is necessary to make read work in git's non-interactive shell
+ read choice < /dev/tty
+
+ case $choice in
+ [Cc]* )
+ echo "✅ Continuing commit without lint-fix changes..."
+ sleep 3
+ ;;
+ [Aa]* | * )
+ echo "🛑 Commit aborted. Please review the changes made by lint-fix."
+ echo "💡 You can stage the changes with 'git add .' and commit again."
+ exit 1
+ ;;
+ esac
+fi
+
# Then run Build Architecture Guard
#echo "🏗️ Running Build Architecture Guard..."
diff --git a/src/assets/styles/tailwind.css b/src/assets/styles/tailwind.css
index 03063394..f4e5b371 100644
--- a/src/assets/styles/tailwind.css
+++ b/src/assets/styles/tailwind.css
@@ -38,7 +38,7 @@
}
.dialog {
- @apply bg-white p-4 rounded-lg w-full max-w-lg;
+ @apply bg-white px-4 py-6 rounded-lg w-full max-w-lg max-h-[90%] overflow-y-auto;
}
/* Markdown content styling to restore list elements */
diff --git a/src/components/SetBulkVisibilityDialog.vue b/src/components/BulkMembersDialog.vue
similarity index 53%
rename from src/components/SetBulkVisibilityDialog.vue
rename to src/components/BulkMembersDialog.vue
index ee55022a..dd41e474 100644
--- a/src/components/SetBulkVisibilityDialog.vue
+++ b/src/components/BulkMembersDialog.vue
@@ -3,18 +3,18 @@
- Set Visibility to Meeting Members
+ {{ title }}
- Would you like to make your activities visible to the following
- members? (This will also add them as contacts if they aren't already.)
+ {{ description }}
-
-
+
+
+
@@ -31,14 +31,15 @@
-
+
- No members need visibility settings
+ {{ emptyStateText }}
+
- {{ member.name || SOMEONE_UNNAMED }}
+
+
+ {{ member.name || SOMEONE_UNNAMED }}
+
+
+ DID:
+ {{ member.did }}
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -101,26 +133,19 @@ import { Vue, Component, Prop } from "vue-facing-decorator";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { SOMEONE_UNNAMED } from "@/constants/entities";
-import { setVisibilityUtil } from "@/libs/endorserServer";
+import { MemberData } from "@/interfaces";
+import { setVisibilityUtil, getHeaders } from "@/libs/endorserServer";
import { createNotifyHelpers } from "@/utils/notify";
-interface MemberData {
- did: string;
- name: string;
- isContact: boolean;
- member: {
- memberId: string;
- };
-}
-
@Component({
mixins: [PlatformServiceMixin],
+ emits: ["close"],
})
-export default class SetBulkVisibilityDialog extends Vue {
- @Prop({ default: false }) visible!: boolean;
- @Prop({ default: () => [] }) membersData!: MemberData[];
+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!: (
@@ -132,8 +157,9 @@ export default class SetBulkVisibilityDialog extends Vue {
notify!: ReturnType;
// Component state
+ membersData: MemberData[] = [];
selectedMembers: string[] = [];
- selectionInitialized = false;
+ visible = false;
// Constants
// In Vue templates, imported constants need to be explicitly made available to the template
@@ -158,29 +184,46 @@ export default class SetBulkVisibilityDialog extends Vue {
return selectedCount > 0 && selectedCount < this.membersData.length;
}
- get shouldInitializeSelection() {
- // This method will initialize selection when the dialog opens
- if (!this.selectionInitialized) {
- this.initializeSelection();
- this.selectionInitialized = true;
- }
- return true;
+ 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);
}
- initializeSelection() {
- // Reset selection when dialog opens
- this.selectedMembers = [];
+ open(members: MemberData[]) {
+ this.visible = true;
+ this.membersData = members;
// Select all by default
this.selectedMembers = this.membersData.map((member) => member.did);
}
- resetSelection() {
- this.selectedMembers = [];
- this.selectionInitialized = false;
+ close(notSelectedMemberDids: string[]) {
+ this.visible = false;
+ this.$emit("close", { notSelectedMemberDids: notSelectedMemberDids });
+ }
+
+ cancel() {
+ this.close(this.membersData.map((member) => member.did));
}
toggleSelectAll() {
@@ -208,25 +251,95 @@ export default class SetBulkVisibilityDialog extends Vue {
return this.selectedMembers.includes(memberDid);
}
- async setVisibilityForSelectedMembers() {
+ 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 successCount = 0;
+ let admittedCount = 0;
+ let contactAddedCount = 0;
for (const member of selectedMembers) {
try {
- // If they're not a contact yet, add them as a contact first
+ // 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
+ }
+ }
- successCount++;
+ // 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);
@@ -239,30 +352,47 @@ export default class SetBulkVisibilityDialog extends Vue {
{
group: "alert",
type: "success",
- title: "Visibility Set Successfully",
- text: `Visibility set for ${successCount} member${successCount === 1 ? "" : "s"}.`,
+ title: "Contacts Added Successfully",
+ text: `${contactsAddedCount} member${contactsAddedCount === 1 ? "" : "s"} added as contact${contactsAddedCount === 1 ? "" : "s"}.`,
},
5000,
);
- // Emit success event
- this.$emit("success", successCount);
- this.close();
+ this.close(notSelectedMembers.map((member) => member.did));
} catch (error) {
// eslint-disable-next-line no-console
- console.error("Error setting visibility:", error);
+ console.error("Error adding contacts:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
- text: "Failed to set visibility for some members. Please try again.",
+ 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 = {
@@ -310,24 +440,20 @@ export default class SetBulkVisibilityDialog extends Vue {
}
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: "This user is already your contact, but your activities are not visible to them yet.",
+ text: message,
},
5000,
);
}
-
- close() {
- this.resetSelection();
- this.$emit("close");
- }
-
- cancel() {
- this.close();
- }
}
diff --git a/src/components/FeedFilters.vue b/src/components/FeedFilters.vue
index 91a0db6b..a69aaf0e 100644
--- a/src/components/FeedFilters.vue
+++ b/src/components/FeedFilters.vue
@@ -211,8 +211,6 @@ export default class FeedFilters extends Vue {
}
-
diff --git a/src/components/MembersList.vue b/src/components/MembersList.vue
index e26613bf..f0043040 100644
--- a/src/components/MembersList.vue
+++ b/src/components/MembersList.vue
@@ -1,197 +1,235 @@
-
-
-
-
-
-
-
-
-
-
- {{ decryptionErrorMessage() }}
-
-
-
- You are not currently admitted by the organizer.
-
-
- Your name is not set, so others may not recognize you. Reload this page
- to set it.
+
+
+
+
+
-
-
- Click
-
+
+
+
+ {{ decryptionErrorMessage() }}
+
+
+
+ You are not currently admitted by the organizer.
+
+
+ Your name is not set, so others may not recognize you. Reload this
+ page to set it.
+
+
+
+
-
-
- /
-
+ /
+
+ to add/remove them to/from the meeting.
+