forked from jsnbuchanan/crowd-funder-for-time-pwa
fix: image server references and test configurations - Update image server references to use test server by default for local dev - Fix registration status checks in tests - Remove verbose console logging - Update environment configurations for consistent image server usage - Fix alert handling in contact registration tests - Clean up component lifecycle logging - Add clarifying comments about shared image server usage - Update playwright test configurations for better reliability This commit ensures consistent image server behavior across environments and improves test reliability by properly handling registration status checks and alerts.
344 lines
9.1 KiB
Vue
344 lines
9.1 KiB
Vue
<template>
|
|
<QuickNav selected="Contacts" />
|
|
<TopMessage />
|
|
|
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
|
<!-- Heading -->
|
|
<h1 id="ViewHeading" class="text-4xl text-center font-light mb-8">
|
|
Onboarding Meetings
|
|
</h1>
|
|
|
|
<!-- Loading State -->
|
|
<div v-if="isLoading" class="flex justify-center items-center py-8">
|
|
<fa icon="spinner" class="fa-spin-pulse" />
|
|
</div>
|
|
|
|
<div v-else-if="attendingMeeting">
|
|
<p>You are in this meeting.</p>
|
|
<div
|
|
class="p-4 bg-white rounded-lg shadow hover:shadow-md transition-shadow cursor-pointer"
|
|
@click="promptPassword(attendingMeeting)"
|
|
>
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="text-xl font-medium">{{ attendingMeeting.name }}</h2>
|
|
<button
|
|
@click.stop="leaveMeeting"
|
|
class="text-red-600 hover:text-red-700 p-2"
|
|
title="Leave Meeting"
|
|
>
|
|
<fa icon="right-from-bracket" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Meeting List -->
|
|
<div v-else class="space-y-4">
|
|
<div
|
|
v-for="meeting in meetings"
|
|
:key="meeting.groupId"
|
|
class="p-4 bg-white rounded-lg shadow hover:shadow-md transition-shadow cursor-pointer"
|
|
@click="promptPassword(meeting)"
|
|
>
|
|
<h2 class="text-xl font-medium">{{ meeting.name }}</h2>
|
|
</div>
|
|
|
|
<p v-if="meetings.length === 0" class="text-center text-gray-500 py-8">
|
|
No onboarding meetings available
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Password Dialog -->
|
|
<div
|
|
v-if="showPasswordDialog"
|
|
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4"
|
|
>
|
|
<div class="bg-white rounded-lg p-6 max-w-sm w-full">
|
|
<h3 class="text-lg font-medium mb-4">Enter Meeting Password</h3>
|
|
<input
|
|
ref="passwordInput"
|
|
v-model="password"
|
|
type="text"
|
|
class="w-full px-3 py-2 border rounded-md mb-4"
|
|
placeholder="Enter password"
|
|
@keyup.enter="submitPassword"
|
|
/>
|
|
<div class="flex justify-end space-x-4">
|
|
<button
|
|
@click="cancelPasswordDialog"
|
|
class="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
@click="submitPassword"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
>
|
|
Submit
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Vue } from "vue-facing-decorator";
|
|
import { nextTick } from "vue";
|
|
import { Router } from "vue-router";
|
|
|
|
import QuickNav from "../components/QuickNav.vue";
|
|
import TopMessage from "../components/TopMessage.vue";
|
|
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "../db/index";
|
|
import {
|
|
errorStringForLog,
|
|
getHeaders,
|
|
serverMessageForUser,
|
|
} from "../libs/endorserServer";
|
|
import { encryptMessage } from "../libs/crypto";
|
|
|
|
interface Meeting {
|
|
name: string;
|
|
groupId: number;
|
|
}
|
|
|
|
@Component({
|
|
components: {
|
|
QuickNav,
|
|
TopMessage,
|
|
},
|
|
})
|
|
export default class OnboardMeetingListView extends Vue {
|
|
$notify!: (
|
|
notification: {
|
|
group: string;
|
|
type: string;
|
|
title: string;
|
|
text: string;
|
|
onYes?: () => void;
|
|
yesText?: string;
|
|
},
|
|
timeout?: number,
|
|
) => void;
|
|
|
|
activeDid = "";
|
|
apiServer = "";
|
|
attendingMeeting: Meeting | null = null;
|
|
firstName = "";
|
|
isLoading = false;
|
|
isRegistered = false;
|
|
meetings: Meeting[] = [];
|
|
password = "";
|
|
selectedMeeting: Meeting | null = null;
|
|
showPasswordDialog = false;
|
|
|
|
async created() {
|
|
const settings = await retrieveSettingsForActiveAccount();
|
|
this.activeDid = settings.activeDid || "";
|
|
this.apiServer = settings.apiServer || "";
|
|
this.firstName = settings.firstName || "";
|
|
this.isRegistered = !!settings.isRegistered;
|
|
await this.fetchMeetings();
|
|
}
|
|
|
|
async fetchMeetings() {
|
|
this.isLoading = true;
|
|
try {
|
|
// get the meeting that the user is attending
|
|
const headers = await getHeaders(this.activeDid);
|
|
const response = await this.axios.get(
|
|
this.apiServer + "/api/partner/groupOnboardMember",
|
|
{ headers },
|
|
);
|
|
|
|
if (response.data?.data) {
|
|
// they're in a meeting already
|
|
const attendingMeetingId = response.data.data.groupId;
|
|
// retrieve the meeting details
|
|
const headers2 = await getHeaders(this.activeDid);
|
|
const response2 = await this.axios.get(
|
|
this.apiServer + "/api/partner/groupOnboard/" + attendingMeetingId,
|
|
{ headers: headers2 },
|
|
);
|
|
|
|
if (response2.data?.data) {
|
|
this.attendingMeeting = response2.data.data;
|
|
return;
|
|
} else {
|
|
// this should never happen
|
|
logConsoleAndDb(
|
|
"Error fetching meeting for user after saying they are in one.",
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
|
|
const headers2 = await getHeaders(this.activeDid);
|
|
const response2 = await this.axios.get(
|
|
this.apiServer + "/api/partner/groupsOnboarding",
|
|
{ headers: headers2 },
|
|
);
|
|
|
|
if (response2.data?.data) {
|
|
this.meetings = response2.data.data;
|
|
}
|
|
} catch (error) {
|
|
logConsoleAndDb(
|
|
"Error fetching meetings: " + errorStringForLog(error),
|
|
true,
|
|
);
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text: serverMessageForUser(error) || "Failed to fetch meetings.",
|
|
},
|
|
5000,
|
|
);
|
|
} finally {
|
|
this.isLoading = false;
|
|
}
|
|
}
|
|
|
|
promptPassword(meeting: Meeting) {
|
|
this.password = "";
|
|
this.selectedMeeting = meeting;
|
|
this.showPasswordDialog = true;
|
|
nextTick(() => {
|
|
const input = this.$refs.passwordInput as HTMLInputElement;
|
|
if (input) {
|
|
input.focus();
|
|
}
|
|
});
|
|
}
|
|
|
|
cancelPasswordDialog() {
|
|
this.password = "";
|
|
this.selectedMeeting = null;
|
|
this.showPasswordDialog = false;
|
|
}
|
|
|
|
async submitPassword() {
|
|
if (!this.selectedMeeting) {
|
|
// this should never happen
|
|
logConsoleAndDb(
|
|
"No meeting selected when prompting for password, which should never happen.",
|
|
true,
|
|
);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create member data object
|
|
const memberData = {
|
|
name: this.firstName,
|
|
did: this.activeDid,
|
|
isRegistered: this.isRegistered,
|
|
};
|
|
const memberDataString = JSON.stringify(memberData);
|
|
const encryptedMemberData = await encryptMessage(
|
|
memberDataString,
|
|
this.password,
|
|
);
|
|
|
|
// Get headers for authentication
|
|
const headers = await getHeaders(this.activeDid);
|
|
|
|
// Encrypt the member data
|
|
const postResult = await this.axios.post(
|
|
this.apiServer + "/api/partner/groupOnboardMember",
|
|
{
|
|
groupId: this.selectedMeeting.groupId,
|
|
content: encryptedMemberData,
|
|
},
|
|
{ headers },
|
|
);
|
|
|
|
if (postResult.data && postResult.data.success) {
|
|
// Navigate to members view with password and groupId
|
|
(this.$router as Router).push({
|
|
name: "onboard-meeting-members",
|
|
params: {
|
|
groupId: this.selectedMeeting.groupId.toString(),
|
|
},
|
|
query: {
|
|
password: this.password,
|
|
memberId: postResult.data.memberId,
|
|
},
|
|
});
|
|
|
|
this.cancelPasswordDialog();
|
|
} else {
|
|
throw { response: postResult };
|
|
}
|
|
} catch (error) {
|
|
logConsoleAndDb(
|
|
"Error joining meeting: " + errorStringForLog(error),
|
|
true,
|
|
);
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text:
|
|
serverMessageForUser(error) || "You failed to join the meeting.",
|
|
},
|
|
5000,
|
|
);
|
|
}
|
|
}
|
|
|
|
async leaveMeeting() {
|
|
this.$notify(
|
|
{
|
|
group: "modal",
|
|
type: "confirm",
|
|
title: "Leave Meeting",
|
|
text: "Are you sure you want to leave this meeting?",
|
|
onYes: async () => {
|
|
try {
|
|
const headers = await getHeaders(this.activeDid);
|
|
await this.axios.delete(
|
|
this.apiServer + "/api/partner/groupOnboardMember",
|
|
{ headers },
|
|
);
|
|
|
|
this.attendingMeeting = null;
|
|
await this.fetchMeetings();
|
|
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "success",
|
|
title: "Success",
|
|
text: "You left the meeting.",
|
|
},
|
|
5000,
|
|
);
|
|
} catch (error) {
|
|
logConsoleAndDb(
|
|
"Error leaving meeting: " + errorStringForLog(error),
|
|
true,
|
|
);
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text:
|
|
serverMessageForUser(error) ||
|
|
"You failed to leave the meeting.",
|
|
},
|
|
5000,
|
|
);
|
|
}
|
|
},
|
|
},
|
|
-1,
|
|
);
|
|
}
|
|
}
|
|
</script>
|