Merge branch 'master' into split_build_process

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.
This commit is contained in:
Matthew Raymer
2025-02-17 06:36:40 +00:00
19 changed files with 633 additions and 147 deletions

View File

@@ -1,40 +1,41 @@
<template>
<div class="space-y-4">
<!-- Loading State -->
<div v-if="isLoading" class="flex justify-center items-center py-8">
<div
v-if="isLoading"
class="mt-16 text-center text-4xl bg-slate-400 text-white w-14 py-2.5 rounded-full mx-auto"
>
<fa icon="spinner" class="fa-spin-pulse" />
</div>
<!-- Members List -->
<p
v-if="decryptedMembers.length < members.length"
class="text-center text-red-600 py-4"
>
{{
decryptFailureMessage ||
"Your password failed. Please go back and try again."
}}
</p>
<div v-else>
<div class="text-center text-red-600 py-4">
{{ decryptionErrorMessage() }}
</div>
<div v-else class="space-y-4">
<div v-if="missingMyself" class="py-4 text-red-600">
You are not admitted. The organizer will admit you.
You are not currently admitted by the organizer.
</div>
<div v-if="!firstName" class="py-4 text-red-600">
Your name is not set, so others may not recognize you. Reload this page
to set it.
</div>
<div>
<span
v-if="showOrganizerTools && isOrganizer"
v-if="membersToShow().length > 0 && showOrganizerTools && isOrganizer"
class="inline-flex items-center flex-wrap"
>
<span class="inline-flex items-center">
Use
&bull; Click
<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"
>
<fa icon="plus" class="text-sm" />
</span>
and
/
<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"
>
@@ -45,8 +46,11 @@
</span>
</div>
<div>
<span class="inline-flex items-center">
Use
<span
v-if="membersToShow().length > 0"
class="inline-flex items-center"
>
&bull; Click
<span
class="mx-2 w-8 h-8 flex items-center justify-center rounded-full bg-green-100 text-green-600"
>
@@ -56,7 +60,8 @@
</span>
</div>
<div v-if="members.length > 0" class="flex justify-center">
<div class="flex justify-center">
<!-- always have at least one refresh button even without members in case the organizer changes the password -->
<button
@click="fetchMembers"
class="w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
@@ -68,7 +73,7 @@
<div
v-for="member in membersToShow()"
:key="member.member.memberId"
class="p-4 bg-gray-50 rounded-lg"
class="mt-2 p-4 bg-gray-50 rounded-lg"
>
<div class="flex items-center justify-between">
<div class="flex items-center">
@@ -131,7 +136,7 @@
{{ member.did }}
</p>
</div>
<div v-if="members.length > 0" class="flex justify-center mt-4">
<div v-if="membersToShow().length > 0" class="flex justify-center mt-4">
<button
@click="fetchMembers"
class="w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
@@ -171,13 +176,13 @@ interface Member {
admitted: boolean;
content: string;
memberId: number;
registered: boolean;
}
interface DecryptedMember {
member: Member;
name: string;
did: string;
isRegistered: boolean;
}
@Component
@@ -187,16 +192,15 @@ export default class MembersList extends Vue {
libsUtil = libsUtil;
@Prop({ required: true }) password!: string;
@Prop({ default: "Your password failed. Please go back and try again." })
decryptFailureMessage!: string;
@Prop({ default: false }) showOrganizerTools!: boolean;
decryptedMembers: DecryptedMember[] = [];
missingPassword = false;
missingMyself = false;
isLoading = false;
firstName = "";
isLoading = true;
isOrganizer = false;
members: Member[] = [];
missingPassword = false;
missingMyself = false;
activeDid = "";
apiServer = "";
contacts: Array<Contact> = [];
@@ -205,13 +209,14 @@ export default class MembersList extends Vue {
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.firstName = settings.firstName || "";
await this.fetchMembers();
await this.loadContacts();
}
async fetchMembers() {
this.isLoading = true;
try {
this.isLoading = true;
const headers = await getHeaders(this.activeDid);
const response = await this.axios.get(
`${this.apiServer}/api/partner/groupOnboardMembers`,
@@ -258,6 +263,7 @@ export default class MembersList extends Vue {
member: member,
name: content.name,
did: content.did,
isRegistered: !!content.isRegistered,
});
if (isFirstEntry && content.did === this.activeDid) {
this.isOrganizer = true;
@@ -273,6 +279,28 @@ export default class MembersList extends Vue {
this.missingMyself = !foundMyself;
}
decryptionErrorMessage(): string {
if (this.isOrganizer) {
if (this.decryptedMembers.length < this.members.length) {
return "Some members have data that cannot be decrypted with that password.";
} else {
// the lists must be equal
return "";
}
} else {
// non-organizers should only see problems if the first (organizer) member is not decrypted
if (
this.decryptedMembers.length === 0 ||
this.decryptedMembers[0].member.memberId !== this.members[0].memberId
) {
return "Your password is not the same as the organizer. Reload or have them check their password.";
} else {
// the first (organizer) member was decrypted OK
return "";
}
}
}
membersToShow(): DecryptedMember[] {
if (this.isOrganizer) {
if (this.showOrganizerTools) {
@@ -293,7 +321,7 @@ export default class MembersList extends Vue {
group: "alert",
type: "info",
title: "Admission info",
text: "This is to register people and 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.",
text: "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.",
},
10000,
);
@@ -331,9 +359,9 @@ export default class MembersList extends Vue {
return this.contacts.find((contact) => contact.did === did);
}
checkWhetherContactBeforeAdmitting(member: DecryptedMember) {
const contact = this.getContactFor(member.did);
if (!member.member.admitted && !contact) {
checkWhetherContactBeforeAdmitting(decrMember: DecryptedMember) {
const contact = this.getContactFor(decrMember.did);
if (!decrMember.member.admitted && !contact) {
// If not a contact, show confirmation dialog
this.$notify(
{
@@ -344,9 +372,9 @@ export default class MembersList extends Vue {
yesText: "Add as Contact",
noText: "Skip Adding Contact",
onYes: async () => {
await this.addAsContact(member);
await this.addAsContact(decrMember);
// After adding as contact, proceed with admission
await this.toggleAdmission(member);
await this.toggleAdmission(decrMember);
},
onNo: async () => {
// If they choose not to add as contact, show second confirmation
@@ -355,10 +383,10 @@ export default class MembersList extends Vue {
group: "modal",
type: "confirm",
title: "Continue Without Adding?",
text: "Are you sure you want to proceed with admission even though they are not a contact?",
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.",
yesText: "Continue",
onYes: async () => {
await this.toggleAdmission(member);
await this.toggleAdmission(decrMember);
},
onCancel: async () => {
// Do nothing, effectively canceling the operation
@@ -372,61 +400,72 @@ export default class MembersList extends Vue {
);
} else {
// If already a contact, proceed directly with admission
this.toggleAdmission(member);
this.toggleAdmission(decrMember);
}
}
async toggleAdmission(member: DecryptedMember) {
async toggleAdmission(decrMember: DecryptedMember) {
try {
const headers = await getHeaders(this.activeDid);
await this.axios.put(
`${this.apiServer}/api/partner/groupOnboardMember/${member.member.memberId}`,
{ admitted: !member.member.admitted },
`${this.apiServer}/api/partner/groupOnboardMember/${decrMember.member.memberId}`,
{ admitted: !decrMember.member.admitted },
{ headers },
);
// Update local state
member.member.admitted = !member.member.admitted;
decrMember.member.admitted = !decrMember.member.admitted;
const oldContact = this.getContactFor(member.did);
const oldContact = this.getContactFor(decrMember.did);
// if admitted, now register that user if they are not registered
if (member.member.admitted && !oldContact?.registered) {
if (
decrMember.member.admitted &&
!decrMember.isRegistered &&
!oldContact?.registered
) {
const contactOldOrNew: Contact = oldContact || {
did: member.did,
name: member.name,
did: decrMember.did,
name: decrMember.name,
};
const result = await register(
this.activeDid,
this.apiServer,
this.axios,
contactOldOrNew,
);
if (result.success) {
member.member.registered = true;
if (oldContact) {
await db.contacts.update(member.did, { registered: true });
oldContact.registered = true;
}
this.$notify(
{
group: "alert",
type: "success",
title: "Registered",
text: "Besides being admitted, they were also registered.",
},
3000,
try {
const result = await register(
this.activeDid,
this.apiServer,
this.axios,
contactOldOrNew,
);
} else {
const additionalInfo = result.error || "";
if (result.success) {
decrMember.isRegistered = true;
if (oldContact) {
await db.contacts.update(decrMember.did, { registered: true });
oldContact.registered = true;
}
this.$notify(
{
group: "alert",
type: "success",
title: "Registered",
text: "Besides being admitted, they were also registered.",
},
3000,
);
} else {
throw result;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
// registration failure is likely explained by a message from the server
const additionalInfo =
serverMessageForUser(error) || error?.error || "";
this.$notify(
{
group: "alert",
type: "danger",
type: "warning",
title: "Registration failed",
text:
"They were admitted, but registration failed. You can try again, or register from your contacts screen. " +
"They were admitted to the meeting. However, registration failed. You can register them from the contacts screen. " +
additionalInfo,
},
10000,
12000,
);
}
}