feat: implement member visibility dialog with checkbox selection and refresh #208

Open
jose wants to merge 9 commits from meeting-members-set-visibility into master
  1. 312
      src/components/MembersList.vue
  2. 333
      src/components/SetBulkVisibilityDialog.vue
  3. 9
      src/constants/app.ts
  4. 4
      src/views/NotFoundView.vue
  5. 2
      src/views/OnboardMeetingMembersView.vue
  6. 10
      src/views/OnboardMeetingSetupView.vue

312
src/components/MembersList.vue

@ -11,7 +11,7 @@
<!-- Members List --> <!-- Members List -->
<div v-else> <div v-else>
<div class="text-center text-red-600 py-4"> <div class="text-center text-red-600 my-4">
{{ decryptionErrorMessage() }} {{ decryptionErrorMessage() }}
</div> </div>
@ -23,97 +23,94 @@
to set it. to set it.
</div> </div>
<div> <ul class="list-disc text-sm ps-4 space-y-2 mb-4">
<span <li
v-if="membersToShow().length > 0 && showOrganizerTools && isOrganizer" v-if="membersToShow().length > 0 && showOrganizerTools && isOrganizer"
class="inline-flex items-center flex-wrap"
> >
<span class="inline-flex items-center"> Click
&bull; Click
<span <span
class="mx-2 min-w-[24px] min-h-[24px] w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600" class="inline-block w-5 h-5 rounded-full bg-blue-100 text-blue-600 text-center"
> >
<font-awesome icon="plus" class="text-sm" /> <font-awesome icon="plus" class="text-sm" />
</span> </span>
/ /
<span <span
class="mx-2 min-w-[24px] min-h-[24px] w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600" class="inline-block w-5 h-5 rounded-full bg-blue-100 text-blue-600 text-center"
> >
<font-awesome icon="minus" class="text-sm" /> <font-awesome icon="minus" class="text-sm" />
</span> </span>
to add/remove them to/from the meeting. to add/remove them to/from the meeting.
</span> </li>
</span> <li v-if="membersToShow().length > 0">
</div> Click
<div>
<span
v-if="membersToShow().length > 0"
class="inline-flex items-center"
>
&bull; Click
<span <span
class="mx-2 w-8 h-8 flex items-center justify-center rounded-full bg-green-100 text-green-600" class="inline-block w-5 h-5 rounded-full bg-green-100 text-green-600 text-center"
> >
<font-awesome icon="circle-user" class="text-xl" /> <font-awesome icon="circle-user" class="text-sm" />
</span> </span>
to add them to your contacts. to add them to your contacts.
</span> </li>
</div> </ul>
<div class="flex justify-center"> <div class="flex justify-between">
<!-- <!--
always have at least one refresh button even without members in case the organizer always have at least one refresh button even without members in case the organizer
changes the password changes the password
--> -->
<button <button
class="btn-action-refresh" class="text-sm bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
title="Refresh members list" title="Refresh members list now"
@click="fetchMembers" @click="manualRefresh"
> >
<font-awesome icon="rotate" :class="{ 'fa-spin': isLoading }" /> <font-awesome icon="rotate" :class="{ 'fa-spin': isLoading }" />
Refresh
<span class="text-xs">({{ countdownTimer }}s)</span>
</button> </button>
</div> </div>
<div <ul
v-if="membersToShow().length > 0"
class="border-t border-slate-300 my-2"
>
<li
v-for="member in membersToShow()" v-for="member in membersToShow()"
:key="member.member.memberId" :key="member.member.memberId"
class="mt-2 p-4 bg-gray-50 rounded-lg" class="border-b border-slate-300 py-1.5"
> >
<div class="flex items-center justify-between"> <div class="flex items-center gap-2 justify-between">
<div class="flex items-center"> <div class="flex items-center gap-1 overflow-hidden">
<h3 class="text-lg font-medium"> <h3 class="font-semibold truncate">
{{ member.name || unnamedMember }} {{ member.name || unnamedMember }}
</h3> </h3>
<div <div
v-if="!getContactFor(member.did) && member.did !== activeDid" v-if="!getContactFor(member.did) && member.did !== activeDid"
class="flex justify-end" class="flex items-center gap-1"
> >
<button <button
class="btn-add-contact" class="btn-add-contact"
title="Add as contact" title="Add as contact"
@click="addAsContact(member)" @click="addAsContact(member)"
> >
<font-awesome icon="circle-user" class="text-xl" /> <font-awesome icon="circle-user" />
</button> </button>
</div>
<button <button
v-if="member.did !== activeDid"
class="btn-info-contact" class="btn-info-contact"
title="Contact info" title="Contact Info"
@click=" @click="
informAboutAddingContact( informAboutAddingContact(
getContactFor(member.did) !== undefined, getContactFor(member.did) !== undefined,
) )
" "
> >
<font-awesome icon="circle-info" class="text-base" /> <font-awesome icon="circle-info" class="text-sm" />
</button> </button>
</div> </div>
<div class="flex"> </div>
<span <span
v-if=" v-if="
showOrganizerTools && isOrganizer && member.did !== activeDid showOrganizerTools && isOrganizer && member.did !== activeDid
" "
class="flex items-center" class="flex items-center gap-1"
> >
<button <button
class="btn-admission" class="btn-admission"
@ -124,30 +121,37 @@
> >
<font-awesome <font-awesome
:icon="member.member.admitted ? 'minus' : 'plus'" :icon="member.member.admitted ? 'minus' : 'plus'"
class="text-sm"
/> />
</button> </button>
<button <button
class="btn-info-admission" class="btn-info-admission"
title="Admission info" title="Admission Info"
@click="informAboutAdmission()" @click="informAboutAdmission()"
> >
<font-awesome icon="circle-info" class="text-base" /> <font-awesome icon="circle-info" class="text-sm" />
</button> </button>
</span> </span>
</div> </div>
</div> <p class="text-xs text-gray-600 truncate">
<p class="text-sm text-gray-600 truncate">
{{ member.did }} {{ member.did }}
</p> </p>
</div> </li>
<div v-if="membersToShow().length > 0" class="flex justify-center mt-4"> </ul>
<div v-if="membersToShow().length > 0" class="flex justify-between">
<!--
always have at least one refresh button even without members in case the organizer
changes the password
-->
<button <button
class="btn-action-refresh" class="text-sm bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
title="Refresh members list" title="Refresh members list now"
@click="fetchMembers" @click="manualRefresh"
> >
<font-awesome icon="rotate" :class="{ 'fa-spin': isLoading }" /> <font-awesome icon="rotate" :class="{ 'fa-spin': isLoading }" />
Refresh
<span class="text-xs">({{ countdownTimer }}s)</span>
</button> </button>
</div> </div>
@ -156,6 +160,15 @@
</p> </p>
</div> </div>
</div> </div>
<!-- Set Visibility Dialog Component -->
<SetBulkVisibilityDialog
:visible="showSetVisibilityDialog"
:members-data="visibilityDialogMembers"
:active-did="activeDid"
:api-server="apiServer"
@close="closeSetVisibilityDialog"
/>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -178,6 +191,7 @@ import {
NOTIFY_CONTINUE_WITHOUT_ADDING, NOTIFY_CONTINUE_WITHOUT_ADDING,
} from "@/constants/notifications"; } from "@/constants/notifications";
import { SOMEONE_UNNAMED } from "@/constants/entities"; import { SOMEONE_UNNAMED } from "@/constants/entities";
import SetBulkVisibilityDialog from "./SetBulkVisibilityDialog.vue";
interface Member { interface Member {
admitted: boolean; admitted: boolean;
@ -193,6 +207,9 @@ interface DecryptedMember {
} }
@Component({ @Component({
components: {
SetBulkVisibilityDialog,
},
mixins: [PlatformServiceMixin], mixins: [PlatformServiceMixin],
}) })
export default class MembersList extends Vue { export default class MembersList extends Vue {
@ -219,8 +236,25 @@ export default class MembersList extends Vue {
missingMyself = false; missingMyself = false;
activeDid = ""; activeDid = "";
apiServer = ""; apiServer = "";
// Set Visibility Dialog state
showSetVisibilityDialog = false;
visibilityDialogMembers: Array<{
did: string;
name: string;
isContact: boolean;
member: { memberId: string };
}> = [];
contacts: Array<Contact> = []; contacts: Array<Contact> = [];
// Auto-refresh functionality
countdownTimer = 10;
autoRefreshInterval: NodeJS.Timeout | null = null;
lastRefreshTime = 0;
// Track previous visibility members to detect changes
previousVisibilityMembers: string[] = [];
/** /**
* Get the unnamed member constant * Get the unnamed member constant
*/ */
@ -242,6 +276,21 @@ export default class MembersList extends Vue {
this.firstName = settings.firstName || ""; this.firstName = settings.firstName || "";
await this.fetchMembers(); await this.fetchMembers();
await this.loadContacts(); await this.loadContacts();
// Start auto-refresh
this.startAutoRefresh();
// Check if we should show the visibility dialog on initial load
this.checkAndShowVisibilityDialog();
}
async refreshData() {
// Force refresh both contacts and members
await this.loadContacts();
await this.fetchMembers();
// Check if we should show the visibility dialog after refresh
this.checkAndShowVisibilityDialog();
} }
async fetchMembers() { async fetchMembers() {
@ -344,7 +393,7 @@ export default class MembersList extends Vue {
informAboutAdmission() { informAboutAdmission() {
this.notify.info( this.notify.info(
"This is to register people in Time Safari and to admit them to the meeting. A '+' symbol means they are not yet admitted and you can register and admit them. A '-' means you can remove them, but they will stay registered.", "This is to register people in Time Safari and to admit them to the meeting. A (+) symbol means they are not yet admitted and you can register and admit them. A (-) symbol means you can remove them, but they will stay registered.",
TIMEOUTS.VERY_LONG, TIMEOUTS.VERY_LONG,
); );
} }
@ -371,6 +420,80 @@ export default class MembersList extends Vue {
return this.contacts.find((contact) => contact.did === did); return this.contacts.find((contact) => contact.did === did);
} }
getMembersForVisibility() {
return this.decryptedMembers
.filter((member) => {
// Exclude the current user
if (member.did === this.activeDid) {
return false;
}
const contact = this.getContactFor(member.did);
// Include members who:
// 1. Haven't been added as contacts yet, OR
// 2. Are contacts but don't have visibility set (seesMe property)
return !contact || !contact.seesMe;
})
.map((member) => ({
did: member.did,
name: member.name,
isContact: !!this.getContactFor(member.did),
member: {
memberId: member.member.memberId.toString(),
},
}));
}
/**
* 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)
*/
shouldShowVisibilityDialog(): boolean {
const currentMembers = this.getMembersForVisibility();
if (currentMembers.length === 0) {
return false;
}
// If no previous members tracked, show dialog
if (this.previousVisibilityMembers.length === 0) {
return true;
}
// Check if new members have been added (not just any change)
const currentMemberIds = currentMembers.map((m) => m.did);
const previousMemberIds = this.previousVisibilityMembers;
// Find new members (members in current but not in previous)
const newMembers = currentMemberIds.filter(
(id) => !previousMemberIds.includes(id),
);
// Only show dialog if there are new members added
return newMembers.length > 0;
}
/**
* Update the tracking of previous visibility members
*/
updatePreviousVisibilityMembers() {
const currentMembers = this.getMembersForVisibility();
this.previousVisibilityMembers = currentMembers.map((m) => m.did);
}
/**
* Show the visibility dialog if conditions are met
*/
checkAndShowVisibilityDialog() {
if (this.shouldShowVisibilityDialog()) {
this.showSetBulkVisibilityDialog();
}
this.updatePreviousVisibilityMembers();
}
checkWhetherContactBeforeAdmitting(decrMember: DecryptedMember) { checkWhetherContactBeforeAdmitting(decrMember: DecryptedMember) {
const contact = this.getContactFor(decrMember.did); const contact = this.getContactFor(decrMember.did);
if (!decrMember.member.admitted && !contact) { if (!decrMember.member.admitted && !contact) {
@ -508,6 +631,79 @@ export default class MembersList extends Vue {
this.notify.error(message, TIMEOUTS.LONG); this.notify.error(message, TIMEOUTS.LONG);
} }
} }
showSetBulkVisibilityDialog() {
// Filter members to show only those who need visibility set
const membersForVisibility = this.getMembersForVisibility();
// Pause auto-refresh when dialog opens
this.stopAutoRefresh();
// Open the dialog directly
this.visibilityDialogMembers = membersForVisibility;
this.showSetVisibilityDialog = true;
}
startAutoRefresh() {
this.lastRefreshTime = Date.now();
this.countdownTimer = 10;
this.autoRefreshInterval = setInterval(() => {
const now = Date.now();
const timeSinceLastRefresh = (now - this.lastRefreshTime) / 1000;
if (timeSinceLastRefresh >= 10) {
// Time to refresh
this.refreshData();
this.lastRefreshTime = now;
this.countdownTimer = 10;
} else {
// Update countdown
this.countdownTimer = Math.max(
0,
Math.round(10 - timeSinceLastRefresh),
);
}
}, 1000); // Update every second
}
stopAutoRefresh() {
if (this.autoRefreshInterval) {
clearInterval(this.autoRefreshInterval);
this.autoRefreshInterval = null;
}
}
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();
// Always show dialog on manual refresh if there are members for visibility
if (this.getMembersForVisibility().length > 0) {
this.showSetBulkVisibilityDialog();
}
}
// Set Visibility Dialog methods
closeSetVisibilityDialog() {
this.showSetVisibilityDialog = false;
this.visibilityDialogMembers = [];
// Refresh data when dialog is closed
this.refreshData();
// Resume auto-refresh when dialog is closed
this.startAutoRefresh();
}
beforeDestroy() {
this.stopAutoRefresh();
}
} }
</script> </script>
@ -522,29 +718,23 @@ export default class MembersList extends Vue {
.btn-add-contact { .btn-add-contact {
/* stylelint-disable-next-line at-rule-no-unknown */ /* stylelint-disable-next-line at-rule-no-unknown */
@apply ml-2 w-8 h-8 flex items-center justify-center rounded-full @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 bg-green-100 text-green-600 hover:bg-green-200 hover:text-green-800
transition-colors; transition-colors;
} }
.btn-info-contact { .btn-info-contact,
.btn-info-admission {
/* stylelint-disable-next-line at-rule-no-unknown */ /* stylelint-disable-next-line at-rule-no-unknown */
@apply ml-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full @apply w-6 h-6 flex items-center justify-center rounded-full
bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 bg-slate-100 text-slate-400 hover:text-slate-600
transition-colors; transition-colors;
} }
.btn-admission { .btn-admission {
/* stylelint-disable-next-line at-rule-no-unknown */ /* stylelint-disable-next-line at-rule-no-unknown */
@apply mr-2 w-6 h-6 flex items-center justify-center rounded-full @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 bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800
transition-colors; transition-colors;
} }
.btn-info-admission {
/* stylelint-disable-next-line at-rule-no-unknown */
@apply mr-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full
bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800
transition-colors;
}
</style> </style>

333
src/components/SetBulkVisibilityDialog.vue

@ -0,0 +1,333 @@
<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">
Set Visibility to Meeting Members
</h3>
<p class="text-sm mb-4">
Would you like to <b>make your activities visible</b> to the following
members? (This will also add them as contacts if they aren't already.)
</p>
<!-- Custom table area - you can customize this -->
<div v-if="shouldInitializeSelection" 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 need visibility settings
</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>
</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"
>
Set Visibility
</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"
>
{{
membersData && membersData.length > 0 ? "Maybe Later" : "Cancel"
}}
</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 { setVisibilityUtil } from "@/libs/endorserServer";
import { createNotifyHelpers } from "@/utils/notify";
interface MemberData {
did: string;
name: string;
isContact: boolean;
member: {
memberId: string;
};
}
@Component({
mixins: [PlatformServiceMixin],
})
export default class SetBulkVisibilityDialog extends Vue {
@Prop({ default: false }) visible!: boolean;
@Prop({ default: () => [] }) membersData!: MemberData[];
@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
selectedMembers: string[] = [];
selectionInitialized = 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;
}
get shouldInitializeSelection() {
// This method will initialize selection when the dialog opens
if (!this.selectionInitialized) {
this.initializeSelection();
this.selectionInitialized = true;
}
return true;
}
created() {
this.notify = createNotifyHelpers(this.$notify);
}
initializeSelection() {
// Reset selection when dialog opens
this.selectedMembers = [];
// Select all by default
this.selectedMembers = this.membersData.map((member) => member.did);
}
resetSelection() {
this.selectedMembers = [];
this.selectionInitialized = false;
}
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),
);
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,
);
// Emit success event
this.$emit("success", successCount);
this.close();
} 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,
);
}
close() {
this.resetSelection();
this.$emit("close");
}
cancel() {
this.close();
}
}
</script>

9
src/constants/app.ts

@ -59,7 +59,7 @@ export const PASSKEYS_ENABLED =
export interface NotificationIface { export interface NotificationIface {
group: string; // "alert" | "modal" group: string; // "alert" | "modal"
type: string; // "toast" | "info" | "success" | "warning" | "danger" type: string; // "toast" | "info" | "success" | "warning" | "danger"
title: string; title?: string;
text?: string; text?: string;
callback?: (success: boolean) => Promise<void>; // if this triggered an action callback?: (success: boolean) => Promise<void>; // if this triggered an action
noText?: string; noText?: string;
@ -68,4 +68,11 @@ export interface NotificationIface {
onYes?: () => Promise<void>; onYes?: () => Promise<void>;
promptToStopAsking?: boolean; promptToStopAsking?: boolean;
yesText?: string; yesText?: string;
membersData?: Array<{
member: { admitted: boolean; content: string; memberId: number };
name: string;
did: string;
isContact: boolean;
contact?: { did: string; name?: string; seesMe?: boolean };
}>; // For passing member data to visibility dialog
} }

4
src/views/NotFoundView.vue

@ -60,7 +60,9 @@
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
stroke-width="2" stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2
2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1
1 0 011 1v4a1 1 0 001 1m-6 0h6"
></path> ></path>
</svg> </svg>
Go Home Go Home

2
src/views/OnboardMeetingMembersView.vue

@ -77,6 +77,7 @@ import {
} from "../libs/endorserServer"; } from "../libs/endorserServer";
import { generateSaveAndActivateIdentity } from "../libs/util"; import { generateSaveAndActivateIdentity } from "../libs/util";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { NotificationIface } from "../constants/app";
@Component({ @Component({
components: { components: {
@ -97,6 +98,7 @@ export default class OnboardMeetingMembersView extends Vue {
projectLink = ""; projectLink = "";
$route!: RouteLocationNormalizedLoaded; $route!: RouteLocationNormalizedLoaded;
$router!: Router; $router!: Router;
$notify!: (notification: NotificationIface, timeout?: number) => void;
userNameDialog!: InstanceType<typeof UserNameDialog>; userNameDialog!: InstanceType<typeof UserNameDialog>;

10
src/views/OnboardMeetingSetupView.vue

@ -230,10 +230,11 @@
class="mt-8 p-4 border rounded-lg bg-white shadow" class="mt-8 p-4 border rounded-lg bg-white shadow"
> >
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h2 class="text-2xl">Meeting Members</h2> <h2 class="font-bold">Meeting Members</h2>
</div> </div>
<div class="mt-4"> <ul class="list-disc text-sm mt-4 mb-2 ps-4 space-y-2">
&bull; Page for Members <li>
Page for Members:
<span <span
class="ml-4 cursor-pointer text-blue-600" class="ml-4 cursor-pointer text-blue-600"
title="Click to copy link for members" title="Click to copy link for members"
@ -249,7 +250,8 @@
> >
<font-awesome icon="external-link" /> <font-awesome icon="external-link" />
</a> </a>
</div> </li>
</ul>
<MembersList <MembersList
:password="currentMeeting.password || ''" :password="currentMeeting.password || ''"

Loading…
Cancel
Save