Show a "progress" dialog while invites are being sent (#30561)

* InviteDialog: show some words and a spinner while invites are being sent

* MultiInviter-test: avoid building unhandled rejected promises

If we don't handle rejected promises, jest gets confused by them. Instead,
let's create them on-demand.

* Open a "progress" dialog while invites are being sent

* Inhibit invite progress dialog when RoomUpgradeWarning dialog is kept open

... otherwise the `RoomUpgradeWarning` dialog disappears during the invites,
and the tests that assert that it is showing the correct thing fail.
 enter the commit message for your changes. Lines starting

* Switch to compound CSS variables instead of old pcss vars

* update playwright screenshots

* Revert "update playwright screenshots"

This reverts commit b0a15d97f35a088fe5b67009085eab46be1316fd.

* Another go at updating screenshots

* Address review comments

* remove redundant Props
This commit is contained in:
Richard van der Hoff
2025-08-22 16:10:42 +01:00
committed by GitHub
parent c842b615db
commit e8c88918cb
15 changed files with 271 additions and 81 deletions

View File

@@ -16,6 +16,7 @@ import Modal from "../Modal";
import SettingsStore from "../settings/SettingsStore";
import AskInviteAnywayDialog from "../components/views/dialogs/AskInviteAnywayDialog";
import ConfirmUserActionDialog from "../components/views/dialogs/ConfirmUserActionDialog";
import { openInviteProgressDialog } from "../components/views/dialogs/InviteProgressDialog.tsx";
export enum InviteState {
Invited = "invited",
@@ -44,6 +45,12 @@ const USER_BANNED = "IO.ELEMENT.BANNED";
export interface MultiInviterOptions {
/** Optional callback, fired after each invite */
progressCallback?: () => void;
/**
* By default, we will pop up a "Preparing invitations..." dialog while the invites are being sent. Set this to
* `true` to inhibit it (in which case, you probably want to implement another bit of feedback UI).
*/
inhibitProgressDialog?: boolean;
}
/**
@@ -88,49 +95,59 @@ export default class MultiInviter {
this.addresses.push(...addresses);
this.reason = reason;
for (const addr of this.addresses) {
if (getAddressType(addr) === null) {
this.completionStates[addr] = InviteState.Error;
this.errors[addr] = {
errcode: "M_INVALID",
errorText: _t("invite|invalid_address"),
};
}
let closeDialog: (() => void) | undefined;
if (!this.options.inhibitProgressDialog) {
closeDialog = openInviteProgressDialog();
}
for (const addr of this.addresses) {
// don't try to invite it if it's an invalid address
// (it will already be marked as an error though,
// so no need to do so again)
if (getAddressType(addr) === null) {
continue;
try {
for (const addr of this.addresses) {
if (getAddressType(addr) === null) {
this.completionStates[addr] = InviteState.Error;
this.errors[addr] = {
errcode: "M_INVALID",
errorText: _t("invite|invalid_address"),
};
}
}
// don't re-invite (there's no way in the UI to do this, but
// for sanity's sake)
if (this.completionStates[addr] === InviteState.Invited) {
continue;
for (const addr of this.addresses) {
// don't try to invite it if it's an invalid address
// (it will already be marked as an error though,
// so no need to do so again)
if (getAddressType(addr) === null) {
continue;
}
// don't re-invite (there's no way in the UI to do this, but
// for sanity's sake)
if (this.completionStates[addr] === InviteState.Invited) {
continue;
}
await this.doInvite(addr, false);
if (this._fatal) {
// `doInvite` suffered a fatal error. The error should have been recorded in `errors`; it's up
// to the caller to report back to the user.
return this.completionStates;
}
}
await this.doInvite(addr, false);
if (Object.keys(this.errors).length > 0) {
// There were problems inviting some people - see if we can invite them
// without caring if they exist or not.
const unknownProfileUsers = Object.keys(this.errors).filter((a) =>
UNKNOWN_PROFILE_ERRORS.includes(this.errors[a].errcode),
);
if (this._fatal) {
// `doInvite` suffered a fatal error. The error should have been recorded in `errors`; it's up
// to the caller to report back to the user.
return this.completionStates;
}
}
if (Object.keys(this.errors).length > 0) {
// There were problems inviting some people - see if we can invite them
// without caring if they exist or not.
const unknownProfileUsers = Object.keys(this.errors).filter((a) =>
UNKNOWN_PROFILE_ERRORS.includes(this.errors[a].errcode),
);
if (unknownProfileUsers.length > 0) {
await this.handleUnknownProfileUsers(unknownProfileUsers);
if (unknownProfileUsers.length > 0) {
await this.handleUnknownProfileUsers(unknownProfileUsers);
}
}
} finally {
// Remember to close the progress dialog, if we opened one.
closeDialog?.();
}
return this.completionStates;