allow meeting organizer to see info about embeddings, and add match to pages

This commit is contained in:
2026-02-08 13:45:27 -07:00
parent e38b752b27
commit 1c3d449c85
12 changed files with 355 additions and 77 deletions

View File

@@ -27,7 +27,7 @@ Large Components (>500 lines): 5 components (12.5%)
├── GiftedDialog.vue (670 lines) ⚠️ HIGH PRIORITY ├── GiftedDialog.vue (670 lines) ⚠️ HIGH PRIORITY
├── PhotoDialog.vue (669 lines) ⚠️ HIGH PRIORITY ├── PhotoDialog.vue (669 lines) ⚠️ HIGH PRIORITY
├── PushNotificationPermission.vue (660 lines) ⚠️ HIGH PRIORITY ├── PushNotificationPermission.vue (660 lines) ⚠️ HIGH PRIORITY
└── MembersList.vue (550 lines) ⚠️ MODERATE PRIORITY └── MeetingMembersList.vue (550 lines) ⚠️ MODERATE PRIORITY
Medium Components (200-500 lines): 12 components (30%) Medium Components (200-500 lines): 12 components (30%)
├── GiftDetailsStep.vue (450 lines) ├── GiftDetailsStep.vue (450 lines)

View File

@@ -116,7 +116,7 @@ echo "=============================="
# Analyze critical files identified in the assessment # Analyze critical files identified in the assessment
critical_files=( critical_files=(
src/components/MembersList.vue" src/components/MeetingMembersList.vue"
"src/views/ContactsView.vue" "src/views/ContactsView.vue"
src/views/OnboardMeetingSetupView.vue" src/views/OnboardMeetingSetupView.vue"
src/db/databaseUtil.ts" src/db/databaseUtil.ts"

View File

@@ -93,7 +93,7 @@ echo "=========================="
# Critical files from our assessment # Critical files from our assessment
files=( files=(
src/components/MembersList.vue" src/components/MeetingMembersList.vue"
"src/views/ContactsView.vue" "src/views/ContactsView.vue"
src/views/OnboardMeetingSetupView.vue" src/views/OnboardMeetingSetupView.vue"
src/db/databaseUtil.ts" src/db/databaseUtil.ts"

View File

@@ -93,7 +93,7 @@ echo "=========================="
# Critical files from our assessment # Critical files from our assessment
files=( files=(
src/components/MembersList.vue" src/components/MeetingMembersList.vue"
"src/views/ContactsView.vue" "src/views/ContactsView.vue"
src/views/OnboardMeetingSetupView.vue" src/views/OnboardMeetingSetupView.vue"
src/db/databaseUtil.ts" src/db/databaseUtil.ts"

View File

@@ -0,0 +1,245 @@
<template>
<div class="group-onboard-match-display">
<!-- Loading -->
<div
v-if="isLoading"
class="flex items-center justify-center gap-2 py-6 text-slate-600"
>
<font-awesome icon="spinner" class="fa-spin-pulse" />
</div>
<!-- Error -->
<div
v-else-if="errorMessage"
class="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700"
>
Inform the organizer that there was an error. {{ errorMessage }}
</div>
<!-- Matched person -->
<div
v-else-if="matchedPerson"
class="rounded-lg border border-slate-200 bg-white p-4 shadow-sm"
>
<h2 class="mb-3 font-bold text-slate-700">Your Current Match</h2>
<div class="flex items-start gap-3">
<EntityIcon
:contact="matchedPersonContact"
class="!size-14 shrink-0 overflow-hidden rounded-full border border-slate-300 bg-white"
/>
<div class="min-w-0 flex-1">
<p class="font-medium text-slate-900">
{{ matchedPerson.name || "(No name)" }}
</p>
<p class="mt-0.5 truncate text-xs text-slate-500">
{{ matchedPerson.did }}
</p>
<p
v-if="matchedPerson.description"
class="mt-2 line-clamp-3 text-sm text-slate-600"
>
{{ matchedPerson.description }}
</p>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop, Watch } from "vue-facing-decorator";
import {
errorStringForLog,
getHeaders,
serverMessageForUser,
} from "@/libs/endorserServer";
import { decryptMessage } from "@/libs/crypto";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import EntityIcon from "./EntityIcon.vue";
import { Contact } from "@/db/tables/contacts";
import { AxiosErrorResponse } from "@/interfaces";
/** Participant from GET /api/partner/groupOnboardMatch pair */
interface MatchPairParticipant {
issuerDid: string;
content: string;
description?: string;
decryptedContentObject?: {
name: string;
did: string;
isRegistered: boolean;
} | null;
}
interface MatchPair {
pairNumber: number;
similarity: number;
participants: MatchPairParticipant[];
}
/** Normalized matched person for display */
interface MatchedPersonData {
name: string;
did: string;
isRegistered: boolean;
description?: string;
}
@Component({
components: {
EntityIcon,
},
mixins: [PlatformServiceMixin],
})
export default class GroupOnboardMatchDisplay extends Vue {
@Prop({ required: true })
meetingPassword!: string;
/** When provided, used to determine this person's match instead of calling groupOnboardMatch */
@Prop()
matchPairs?: MatchPair[] | null;
activeDid = "";
apiServer = "";
errorMessage = "";
isLoading = true;
matchedPerson: MatchedPersonData | null = null;
/** Pair that contains the current user (for similarity display if needed) */
myPair: MatchPair | null = null;
/** Contact-like object for EntityIcon from matched person */
get matchedPersonContact(): Contact | undefined {
if (!this.matchedPerson) return undefined;
return {
did: this.matchedPerson.did,
name: this.matchedPerson.name,
};
}
async created() {
const settings = await this.$accountSettings();
this.apiServer = settings?.apiServer || "";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity?.activeDid || "";
await this.fetchMatches();
}
@Watch("meetingPassword")
async onPasswordChange() {
if (
this.meetingPassword &&
(this.matchPairs != null || (this.apiServer && this.activeDid))
) {
await this.fetchMatches();
}
}
@Watch("matchPairs")
async onMatchPairsChange() {
if (this.activeDid && this.meetingPassword) {
await this.fetchMatches();
}
}
async fetchMatches(): Promise<void> {
const usePropPairs =
this.matchPairs != null &&
Array.isArray(this.matchPairs) &&
this.matchPairs.length > 0;
const needApi = !usePropPairs;
if (
needApi &&
(!this.meetingPassword?.trim() || !this.apiServer || !this.activeDid)
) {
this.isLoading = false;
this.matchedPerson = null;
this.myPair = null;
this.errorMessage = "";
return;
}
if (usePropPairs && (!this.meetingPassword?.trim() || !this.activeDid)) {
this.isLoading = false;
this.matchedPerson = null;
this.myPair = null;
this.errorMessage = "";
return;
}
this.isLoading = true;
this.errorMessage = "";
this.matchedPerson = null;
this.myPair = null;
try {
let pairs: MatchPair[] | null = null;
if (usePropPairs) {
// Shallow-copy so we can set decryptedContentObject without mutating the prop
pairs = (this.matchPairs ?? []).map((p) => ({
...p,
participants: p.participants.map((part) => ({ ...part })),
}));
} else {
const headers = await getHeaders(this.activeDid);
const response = await this.axios.get(
`${this.apiServer}/api/partner/groupOnboardMatch`,
{ headers },
);
pairs = response?.data?.data?.pairs ?? null;
}
if (!Array.isArray(pairs) || pairs.length === 0) {
this.isLoading = false;
return;
}
// Decrypt each participant's content and find the pair containing this user
for (const pair of pairs) {
if (!pair.participants || pair.participants.length !== 2) continue;
for (const participant of pair.participants) {
try {
const decrypted = await decryptMessage(
participant.content,
this.meetingPassword,
);
participant.decryptedContentObject = JSON.parse(decrypted);
} catch {
participant.decryptedContentObject = null;
}
}
const myIndex = pair.participants.findIndex(
(p) => p.issuerDid === this.activeDid,
);
if (myIndex === -1) continue;
this.myPair = pair;
const other = pair.participants[1 - myIndex];
const obj = other.decryptedContentObject;
this.matchedPerson = {
name: obj?.name ?? "",
did: obj?.did ?? other.issuerDid,
isRegistered: !!obj?.isRegistered,
description: other.description,
};
break;
}
this.isLoading = false;
} catch (error) {
this.$logAndConsole(
"Error fetching group onboard match: " + errorStringForLog(error),
true,
);
this.errorMessage =
serverMessageForUser(error as unknown as AxiosErrorResponse) ||
"Failed to load your match.";
this.matchedPerson = null;
this.myPair = null;
this.isLoading = false;
}
}
}
</script>

View File

@@ -49,9 +49,9 @@
<div class="flex justify-between"> <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="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" 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 now" title="Refresh members list now"
@@ -198,9 +198,9 @@
<div v-if="membersToShow().length > 0" class="flex justify-between"> <div v-if="membersToShow().length > 0" 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="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" 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 now" title="Refresh members list now"
@@ -273,7 +273,7 @@ interface DecryptedMember {
}, },
mixins: [PlatformServiceMixin], mixins: [PlatformServiceMixin],
}) })
export default class MembersList extends Vue { export default class MeetingMembersList extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;
notify!: ReturnType<typeof createNotifyHelpers>; notify!: ReturnType<typeof createNotifyHelpers>;

View File

@@ -448,8 +448,8 @@ export const NOTIFY_UNCONFIRMED_HOURS_DYNAMIC = {
}; };
// Complex modal constants (for raw $notify calls with advanced features) // Complex modal constants (for raw $notify calls with advanced features)
// MembersList.vue complex modals // MeetingMembersList.vue complex modals
// Used in: MembersList.vue (complex modal for adding contacts) // Used in: MeetingMembersList.vue (complex modal for adding contacts)
export const NOTIFY_ADD_CONTACT_FIRST = { export const NOTIFY_ADD_CONTACT_FIRST = {
title: "Add as Contact First?", title: "Add as Contact First?",
text: "This person is not in your contacts. Would you like to add them as a contact first?", text: "This person is not in your contacts. Would you like to add them as a contact first?",
@@ -457,7 +457,7 @@ export const NOTIFY_ADD_CONTACT_FIRST = {
noText: "Skip Adding Contact", noText: "Skip Adding Contact",
}; };
// Used in: MembersList.vue (complex modal for continuing without adding) // Used in: MeetingMembersList.vue (complex modal for continuing without adding)
export const NOTIFY_CONTINUE_WITHOUT_ADDING = { export const NOTIFY_CONTINUE_WITHOUT_ADDING = {
title: "Continue Without Adding?", title: "Continue Without Adding?",
text: "Are you sure you want to proceed with admission? If they are not a contact, you will not know their name after this meeting.", text: "Are you sure you want to proceed with admission? If they are not a contact, you will not know their name after this meeting.",

View File

@@ -1,5 +1,7 @@
export interface UserProfile { export interface UserProfile {
description: string; description: string;
generateEmbedding?: boolean;
embeddingIsForEmptyString?: boolean;
locLat?: number; locLat?: number;
locLon?: number; locLon?: number;
locLat2?: number; locLat2?: number;

View File

@@ -164,7 +164,9 @@
> >
{{ userProfileData.description }} {{ userProfileData.description }}
</p> </p>
<p v-else class="text-slate-500 italic">No description.</p> <p v-else class="text-slate-500 italic">
This person has no profile description or it's not visible to you.
</p>
</div> </div>
</div> </div>
@@ -319,31 +321,54 @@
class="mt-4 pt-4 border-t border-slate-300" class="mt-4 pt-4 border-t border-slate-300"
data-testid="generateEmbeddingSection" data-testid="generateEmbeddingSection"
> >
<label class="block text-sm font-medium text-gray-700 mb-2">
Always generate embedding
</label>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<label class="block text-sm font-medium text-gray-700 mb-2 mt-2">
Always generate embedding
</label>
<button <button
type="button" type="button"
role="switch" role="switch"
:aria-checked="generateEmbedding" :aria-checked="
:disabled="generateEmbeddingSaving || generateEmbeddingLoading" generateEmbedding ?? userProfileData?.generateEmbedding
"
:disabled="generateEmbeddingSaving || userProfileLoading"
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed" class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
:class="generateEmbedding ? 'bg-blue-600' : 'bg-gray-200'" :class="
(generateEmbedding ?? userProfileData?.generateEmbedding)
? 'bg-blue-600'
: 'bg-gray-200'
"
@click="toggleGenerateEmbedding" @click="toggleGenerateEmbedding"
> >
<span <span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition" class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition"
:class="generateEmbedding ? 'translate-x-5' : 'translate-x-1'" :class="
(generateEmbedding ?? userProfileData?.generateEmbedding)
? 'translate-x-5'
: 'translate-x-1'
"
/> />
</button> </button>
<span class="text-sm text-gray-600"> <span class="text-sm text-gray-600">
{{ generateEmbedding ? "On" : "Off" }} {{
<span v-if="generateEmbeddingLoading" class="ml-1">(loading)</span> (generateEmbedding ?? userProfileData?.generateEmbedding)
? "On"
: "Off"
}}
<span v-if="userProfileLoading" class="ml-1">(loading…)</span>
<span v-else-if="generateEmbeddingSaving" class="ml-1" <span v-else-if="generateEmbeddingSaving" class="ml-1"
>(saving…)</span >(saving…)</span
> >
</span> </span>
<span class="text-sm text-gray-600">
{{
userProfileData?.embeddingIsForEmptyString == null
? ""
: userProfileData?.embeddingIsForEmptyString
? "- Embedding is for blank description"
: "- Embedding is for non-blank description"
}}
</span>
</div> </div>
</div> </div>
@@ -494,7 +519,7 @@ export default class DIDView extends Vue {
contactLabels: string[] = []; contactLabels: string[] = [];
contactYaml = ""; contactYaml = "";
generateEmbedding: boolean | null = null; generateEmbedding: boolean | null = null; // used when there is no profile
generateEmbeddingLoading = false; generateEmbeddingLoading = false;
generateEmbeddingSaving = false; generateEmbeddingSaving = false;
hitEnd = false; hitEnd = false;
@@ -509,7 +534,6 @@ export default class DIDView extends Vue {
showUserProfile = false; showUserProfile = false;
userProfileData: UserProfile | null = null; userProfileData: UserProfile | null = null;
userProfileError: string | null = null; userProfileError: string | null = null;
userProfileFetched = false;
userProfileLoading = false; userProfileLoading = false;
viewingDid?: string; viewingDid?: string;
@@ -543,7 +567,7 @@ export default class DIDView extends Vue {
await this.loadClaimsAbout(); await this.loadClaimsAbout();
await this.checkIfOwnDID(); await this.checkIfOwnDID();
if (this.showGeneralAdvanced && this.activeDid) { if (this.showGeneralAdvanced && this.activeDid) {
await this.loadGenerateEmbeddingState(); await this.loadUserProfile();
} }
} }
} }
@@ -623,48 +647,36 @@ export default class DIDView extends Vue {
this.isMyDid = allAccountDids.includes(this.viewingDid); this.isMyDid = allAccountDids.includes(this.viewingDid);
} }
/**
* Loads partner profile generateEmbedding state for the viewed DID (when showGeneralAdvanced).
*/
private async loadGenerateEmbeddingState() {
if (!this.viewingDid || !this.activeDid) return;
this.generateEmbeddingLoading = true;
try {
const headers = await getHeaders(this.activeDid);
const url = `${this.partnerApiServer}/api/partner/userProfileForIssuer/${encodeURIComponent(this.viewingDid)}`;
const response = await this.axios.get(url, { headers });
const data = response.data?.data;
this.generateEmbedding =
data && typeof data.generateEmbedding === "boolean"
? data.generateEmbedding
: false;
} catch {
this.generateEmbedding = false;
} finally {
this.generateEmbeddingLoading = false;
}
}
/** /**
* Toggles the "always generate embedding" flag for the viewed DID on the partner API. * Toggles the "always generate embedding" flag for the viewed DID on the partner API.
* Only permissioned (admin) users can change this; API returns 403 otherwise. * Only permissioned (admin) users can change this; API returns 403 otherwise.
*/ */
async toggleGenerateEmbedding() { async toggleGenerateEmbedding() {
if ( if (!this.viewingDid || !this.activeDid) {
!this.viewingDid ||
!this.activeDid ||
this.generateEmbeddingSaving ||
this.generateEmbeddingLoading
) {
return; return;
} }
const newValue = !this.generateEmbedding; const newValue = !(this.userProfileData
? this.userProfileData.generateEmbedding
: this.generateEmbedding);
this.generateEmbeddingSaving = true; this.generateEmbeddingSaving = true;
try { try {
const headers = await getHeaders(this.activeDid); const headers = await getHeaders(this.activeDid);
const url = `${this.partnerApiServer}/api/partner/userProfileGenerateEmbedding/${encodeURIComponent(this.viewingDid)}`; const url = `${this.partnerApiServer}/api/partner/userProfileGenerateEmbedding/${encodeURIComponent(this.viewingDid)}`;
await this.axios.put(url, { generateEmbedding: newValue }, { headers }); await this.axios.put(url, { generateEmbedding: newValue }, { headers });
this.generateEmbedding = newValue; if (this.userProfileData) {
this.userProfileData.generateEmbedding = newValue;
this.userProfileData.embeddingIsForEmptyString = newValue; // the server should have generated it or erased it
} else {
this.generateEmbedding = newValue;
if (newValue) {
this.userProfileData = {
description: "",
issuerDid: this.viewingDid,
generateEmbedding: newValue,
embeddingIsForEmptyString: true,
};
}
}
this.notify.success( this.notify.success(
newValue newValue
? "Contact tagged to always generate embedding." ? "Contact tagged to always generate embedding."
@@ -719,7 +731,7 @@ export default class DIDView extends Vue {
*/ */
toggleUserProfile() { toggleUserProfile() {
this.showUserProfile = !this.showUserProfile; this.showUserProfile = !this.showUserProfile;
if (this.showUserProfile && !this.userProfileFetched) { if (this.showUserProfile && !this.userProfileData) {
this.loadUserProfile(); this.loadUserProfile();
} }
} }
@@ -730,7 +742,6 @@ export default class DIDView extends Vue {
*/ */
async loadUserProfile() { async loadUserProfile() {
if (!this.viewingDid || !this.activeDid) return; if (!this.viewingDid || !this.activeDid) return;
this.userProfileFetched = true;
this.userProfileLoading = true; this.userProfileLoading = true;
this.userProfileError = null; this.userProfileError = null;
this.userProfileData = null; this.userProfileData = null;

View File

@@ -40,8 +40,16 @@
</div> </div>
</div> </div>
<!-- Members List --> <div v-else>
<MembersList v-else :password="password" @error="handleError" /> <MeetingMemberMatch
:match-pairs="matchPairs"
:meeting-password="password || ''"
class="mt-4"
/>
<!-- Members List -->
<MeetingMembersList :password="password" @error="handleError" />
</div>
<!-- Project Link Section --> <!-- Project Link Section -->
<div v-if="projectLink" class="mt-8 p-4 border rounded-lg bg-white shadow"> <div v-if="projectLink" class="mt-8 p-4 border rounded-lg bg-white shadow">
@@ -67,7 +75,8 @@ import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import QuickNav from "../components/QuickNav.vue"; import QuickNav from "../components/QuickNav.vue";
import TopMessage from "../components/TopMessage.vue"; import TopMessage from "../components/TopMessage.vue";
import MembersList from "../components/MembersList.vue"; import MeetingMemberMatch from "../components/MeetingMemberMatch.vue";
import MeetingMembersList from "../components/MeetingMembersList.vue";
import UserNameDialog from "../components/UserNameDialog.vue"; import UserNameDialog from "../components/UserNameDialog.vue";
import { encryptMessage } from "../libs/crypto"; import { encryptMessage } from "../libs/crypto";
import { import {
@@ -84,7 +93,8 @@ import { AxiosErrorResponse } from "@/interfaces";
components: { components: {
QuickNav, QuickNav,
TopMessage, TopMessage,
MembersList, MeetingMemberMatch,
MeetingMembersList,
UserNameDialog, UserNameDialog,
}, },
mixins: [PlatformServiceMixin], mixins: [PlatformServiceMixin],

View File

@@ -278,6 +278,13 @@
@close="handleDialogClose" @close="handleDialogClose"
/> />
<div v-if="!!matchPairs">
<MeetingMemberMatch
:meeting-password="currentMeeting.password || ''"
class="mt-4"
/>
</div>
<!-- Members Section --> <!-- Members Section -->
<div <div
v-if="!isLoading && currentMeeting != null && !!currentMeeting.password" v-if="!isLoading && currentMeeting != null && !!currentMeeting.password"
@@ -307,20 +314,19 @@
</li> </li>
</ul> </ul>
<MembersList <MeetingMembersList
ref="membersList" ref="membersList"
:password="currentMeeting.password || ''" :password="currentMeeting.password || ''"
:show-organizer-tools="true" :show-organizer-tools="true"
class="mt-4" class="mt-4"
@error="handleMembersError" @error="handleMembersError"
/> />
</div>
<div class="mt-8 p-4 border rounded-lg bg-white shadow">
<!-- Pairwise matches (organizer only: this page is organizer's meeting) --> <!-- Pairwise matches (organizer only: this page is organizer's meeting) -->
<div class="mt-6 pt-4 border-t border-gray-200"> <div class="border-gray-200">
<h3 class="font-semibold mb-2">Pairs</h3> <h3 class="font-semibold mb-2">Matching Pairs</h3>
<p class="text-sm text-gray-600 mb-3">
Match members by profile similarity
</p>
<div class="flex flex-wrap gap-2 mb-4"> <div class="flex flex-wrap gap-2 mb-4">
<button <button
type="button" type="button"
@@ -428,7 +434,8 @@ import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import QuickNav from "../components/QuickNav.vue"; import QuickNav from "../components/QuickNav.vue";
import TopMessage from "../components/TopMessage.vue"; import TopMessage from "../components/TopMessage.vue";
import MembersList from "../components/MembersList.vue"; import MeetingMembersList from "../components/MeetingMembersList.vue";
import MeetingMemberMatch from "../components/MeetingMemberMatch.vue";
import MeetingProjectDialog from "../components/MeetingProjectDialog.vue"; import MeetingProjectDialog from "../components/MeetingProjectDialog.vue";
import ProjectIcon from "../components/ProjectIcon.vue"; import ProjectIcon from "../components/ProjectIcon.vue";
import { import {
@@ -473,7 +480,7 @@ interface MeetingSetupInputs {
interface MatchPairParticipant { interface MatchPairParticipant {
issuerDid: string; issuerDid: string;
content: string; content: string;
// there's a similar structure in MembersList.vue with extra Member info // there's a similar structure in MeetingMembersList.vue with extra Member info
decryptedContentObject: { decryptedContentObject: {
name: string; name: string;
did: string; did: string;
@@ -492,7 +499,8 @@ interface MatchPair {
components: { components: {
QuickNav, QuickNav,
TopMessage, TopMessage,
MembersList, MeetingMembersList,
MeetingMemberMatch,
MeetingProjectDialog, MeetingProjectDialog,
ProjectIcon, ProjectIcon,
}, },
@@ -1102,7 +1110,6 @@ export default class OnboardMeetingView extends Vue {
); );
this.matchPairs = null; this.matchPairs = null;
this.previousMatchedPairs = []; this.previousMatchedPairs = [];
this.notify.success("Matches cleared.", TIMEOUTS.STANDARD);
} catch (error) { } catch (error) {
this.$logAndConsole( this.$logAndConsole(
"Error clearing matches: " + errorStringForLog(error), "Error clearing matches: " + errorStringForLog(error),
@@ -1187,20 +1194,20 @@ export default class OnboardMeetingView extends Vue {
} }
/** /**
* Handle dialog open event - stop auto-refresh in MembersList * Handle dialog open event - stop auto-refresh in MeetingMembersList
*/ */
handleDialogOpen(): void { handleDialogOpen(): void {
const membersList = this.$refs.membersList as MembersList; const membersList = this.$refs.membersList as MeetingMembersList;
if (membersList) { if (membersList) {
membersList.stopAutoRefresh(); membersList.stopAutoRefresh();
} }
} }
/** /**
* Handle dialog close event - start auto-refresh in MembersList * Handle dialog close event - start auto-refresh in MeetingMembersList
*/ */
handleDialogClose(): void { handleDialogClose(): void {
const membersList = this.$refs.membersList as MembersList; const membersList = this.$refs.membersList as MeetingMembersList;
if (membersList) { if (membersList) {
membersList.startAutoRefresh(); membersList.startAutoRefresh();
} }

View File

@@ -28,6 +28,9 @@ export async function createBuildConfig(platform: string): Promise<UserConfig> {
server: { server: {
port: parseInt(process.env.VITE_PORT || "8080"), port: parseInt(process.env.VITE_PORT || "8080"),
fs: { strict: false }, fs: { strict: false },
//allowedHosts: ['bab3-68-69-173-46.ngrok-free.app'],
//allowedHosts: ['*'],
// CORS headers disabled to allow images from any domain // CORS headers disabled to allow images from any domain
// This means SharedArrayBuffer is unavailable, but absurd-sql // This means SharedArrayBuffer is unavailable, but absurd-sql
// will automatically fall back to IndexedDB mode which still works // will automatically fall back to IndexedDB mode which still works