forked from jsnbuchanan/crowd-funder-for-time-pwa
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:
@@ -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
|
||||
• 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"
|
||||
>
|
||||
• 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user