You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

343 lines
9.2 KiB

<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">
<font-awesome 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"
>
<font-awesome 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>