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

- -
+ +
+ - + + - {{ member.name || SOMEONE_UNNAMED }} +
+
+ {{ member.name || SOMEONE_UNNAMED }} +
+
+ DID: + {{ member.did }} +
+
- + + + + + + +
@@ -31,14 +31,15 @@
- No members need visibility settings + {{ emptyStateText }}
+ +
+
+ +
@@ -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 @@