Apply `strictNullChecks` to `src/components/views/auth/*` (#10299

* Apply `strictNullChecks` to src/components/views/auth/*

* Iterate PR
pull/28788/head^2
Michael Telatynski 2023-03-07 10:45:55 +00:00 committed by GitHub
parent c79eff2292
commit 32aa18ff2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 127 additions and 122 deletions

View File

@ -48,16 +48,6 @@ export type RecursivePartial<T> = {
: T[P];
};
// Inspired by https://stackoverflow.com/a/60206860
export type KeysWithObjectShape<Input> = {
[P in keyof Input]: Input[P] extends object
? // Arrays are counted as objects - exclude them
Input[P] extends Array<unknown>
? never
: P
: never;
}[keyof Input];
export type KeysStartingWith<Input extends object, Str extends string> = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[P in keyof Input]: P extends `${Str}${infer _X}` ? P : never; // we don't use _X

View File

@ -19,7 +19,6 @@ import { Optional } from "matrix-events-sdk";
import { SnakedObject } from "./utils/SnakedObject";
import { IConfigOptions, ISsoRedirectOptions } from "./IConfigOptions";
import { KeysWithObjectShape } from "./@types/common";
// see element-web config.md for docs, or the IConfigOptions interface for dev docs
export const DEFAULTS: IConfigOptions = {
@ -78,10 +77,10 @@ export default class SdkConfig {
return SdkConfig.fallback.get(key, altCaseName);
}
public static getObject<K extends KeysWithObjectShape<IConfigOptions>>(
public static getObject<K extends keyof IConfigOptions>(
key: K,
altCaseName?: string,
): Optional<SnakedObject<IConfigOptions[K]>> {
): Optional<SnakedObject<NonNullable<IConfigOptions[K]>>> {
const val = SdkConfig.get(key, altCaseName);
if (val !== null && val !== undefined) {
return new SnakedObject(val);

View File

@ -80,7 +80,7 @@ interface IProps {
// Called when the stage changes, or the stage's phase changes. First
// argument is the stage, second is the phase. Some stages do not have
// phases and will be counted as 0 (numeric).
onStagePhaseChange?(stage: AuthType, phase: number): void;
onStagePhaseChange?(stage: AuthType | null, phase: number): void;
}
interface IState {
@ -170,7 +170,8 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
busy: true,
});
try {
return await this.props.requestEmailToken(email, secret, attempt, session);
// We know this method only gets called on flows where requestEmailToken is passed but types don't
return await this.props.requestEmailToken!(email, secret, attempt, session);
} finally {
this.setState({
busy: false,
@ -231,7 +232,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
};
private onPhaseChange = (newPhase: number): void => {
this.props.onStagePhaseChange?.(this.state.authStage, newPhase || 0);
this.props.onStagePhaseChange?.(this.state.authStage ?? null, newPhase || 0);
};
private onStageCancel = (): void => {

View File

@ -2015,7 +2015,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
public render(): React.ReactNode {
const fragmentAfterLogin = this.getFragmentAfterLogin();
let view = null;
let view: JSX.Element;
if (this.state.view === Views.LOADING) {
view = (
@ -2132,6 +2132,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
view = <UseCaseSelection onFinished={(useCase): Promise<void> => this.onShowPostLoginScreen(useCase)} />;
} else {
logger.error(`Unknown view ${this.state.view}`);
return null;
}
return (

View File

@ -78,9 +78,9 @@ interface IProps {
}
interface IState {
// true if we're waiting for the user to complete
busy: boolean;
errorText?: ReactNode;
// true if we're waiting for the user to complete
// We remember the values entered by the user because
// the registration form will be unmounted during the
// course of registration, but if there's an error we
@ -88,7 +88,7 @@ interface IState {
// values the user entered still in it. We can keep
// them in this component's state since this component
// persist for the duration of the registration process.
formVals: Record<string, string>;
formVals: Record<string, string | undefined>;
// user-interactive auth
// If we've been given a session ID, we're resuming
// straight back into UI auth
@ -96,9 +96,11 @@ interface IState {
// If set, we've registered but are not going to log
// the user in to their new account automatically.
completedNoSignin: boolean;
flows: {
stages: string[];
}[];
flows:
| {
stages: string[];
}[]
| null;
// We perform liveliness checks later, but for now suppress the errors.
// We also track the server dead errors independently of the regular errors so
// that we can render it differently, and override any other error the user may
@ -158,7 +160,7 @@ export default class Registration extends React.Component<IProps, IState> {
window.removeEventListener("beforeunload", this.unloadCallback);
}
private unloadCallback = (event: BeforeUnloadEvent): string => {
private unloadCallback = (event: BeforeUnloadEvent): string | undefined => {
if (this.state.doingUIAuth) {
event.preventDefault();
event.returnValue = "";
@ -215,7 +217,7 @@ export default class Registration extends React.Component<IProps, IState> {
this.loginLogic.setHomeserverUrl(hsUrl);
this.loginLogic.setIdentityServerUrl(isUrl);
let ssoFlow: ISSOFlow;
let ssoFlow: ISSOFlow | undefined;
try {
const loginFlows = await this.loginLogic.getFlows();
if (serverConfig !== this.latestServerConfig) return; // discard, serverConfig changed from under us
@ -289,6 +291,7 @@ export default class Registration extends React.Component<IProps, IState> {
sendAttempt: number,
sessionId: string,
): Promise<IRequestTokenResponse> => {
if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded");
return this.state.matrixClient.requestRegisterEmailToken(
emailAddress,
clientSecret,
@ -303,6 +306,8 @@ export default class Registration extends React.Component<IProps, IState> {
};
private onUIAuthFinished: InteractiveAuthCallback = async (success, response): Promise<void> => {
if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded");
debuglog("Registration: ui authentication finished: ", { success, response });
if (!success) {
let errorText: ReactNode = (response as Error).message || (response as Error).toString();
@ -327,10 +332,8 @@ export default class Registration extends React.Component<IProps, IState> {
</div>
);
} else if ((response as IAuthData).required_stages?.includes(AuthType.Msisdn)) {
let msisdnAvailable = false;
for (const flow of (response as IAuthData).available_flows) {
msisdnAvailable = msisdnAvailable || flow.stages.includes(AuthType.Msisdn);
}
const flows = (response as IAuthData).available_flows ?? [];
const msisdnAvailable = flows.some((flow) => flow.stages.includes(AuthType.Msisdn));
if (!msisdnAvailable) {
errorText = _t("This server does not support authentication with a phone number.");
}
@ -348,12 +351,16 @@ export default class Registration extends React.Component<IProps, IState> {
return;
}
MatrixClientPeg.setJustRegisteredUserId((response as IAuthData).user_id);
const userId = (response as IAuthData).user_id;
const accessToken = (response as IAuthData).access_token;
if (!userId || !accessToken) throw new Error("Registration failed");
MatrixClientPeg.setJustRegisteredUserId(userId);
const newState: Partial<IState> = {
doingUIAuth: false,
registeredUsername: (response as IAuthData).user_id,
differentLoggedInUserId: null,
differentLoggedInUserId: undefined,
completedNoSignin: false,
// we're still busy until we get unmounted: don't show the registration form again
busy: true,
@ -393,13 +400,13 @@ export default class Registration extends React.Component<IProps, IState> {
// the email, not the client that started the registration flow
await this.props.onLoggedIn(
{
userId: (response as IAuthData).user_id,
userId,
deviceId: (response as IAuthData).device_id,
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
accessToken: (response as IAuthData).access_token,
accessToken,
},
this.state.formVals.password,
this.state.formVals.password!,
);
this.setupPushers();
@ -457,6 +464,8 @@ export default class Registration extends React.Component<IProps, IState> {
};
private makeRegisterRequest = (auth: IAuthData | null): Promise<IAuthData> => {
if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded");
const registerParams: IRegisterRequestParams = {
username: this.state.formVals.username,
password: this.state.formVals.password,
@ -494,7 +503,7 @@ export default class Registration extends React.Component<IProps, IState> {
return sessionLoaded;
};
private renderRegisterComponent(): JSX.Element {
private renderRegisterComponent(): ReactNode {
if (this.state.matrixClient && this.state.doingUIAuth) {
return (
<InteractiveAuth
@ -517,8 +526,8 @@ export default class Registration extends React.Component<IProps, IState> {
<Spinner />
</div>
);
} else if (this.state.flows.length) {
let ssoSection;
} else if (this.state.matrixClient && this.state.flows.length) {
let ssoSection: JSX.Element | undefined;
if (this.state.ssoFlow) {
let continueWithSection;
const providers = this.state.ssoFlow.identity_providers || [];
@ -571,6 +580,8 @@ export default class Registration extends React.Component<IProps, IState> {
</React.Fragment>
);
}
return null;
}
public render(): React.ReactNode {

View File

@ -83,7 +83,7 @@ export const DEFAULT_PHASE = 0;
interface IAuthEntryProps {
matrixClient: MatrixClient;
loginType: string;
authSessionId: string;
authSessionId?: string;
errorText?: string;
errorCode?: string;
// Is the auth logic currently waiting for something to happen?
@ -120,7 +120,7 @@ export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswor
type: AuthType.Password,
// TODO: Remove `user` once servers support proper UIA
// See https://github.com/vector-im/element-web/issues/10312
user: this.props.matrixClient.credentials.userId,
user: this.props.matrixClient.credentials.userId ?? undefined,
identifier: {
type: "m.id.user",
user: this.props.matrixClient.credentials.userId,
@ -286,7 +286,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
// }
// }
const allPolicies = this.props.stageParams.policies || {};
const allPolicies = this.props.stageParams?.policies || {};
const prefLang = SettingsStore.getValue("language");
const initToggles: Record<string, boolean> = {};
const pickedPolicies: {
@ -300,12 +300,12 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
// Pick a language based on the user's language, falling back to english,
// and finally to the first language available. If there's still no policy
// available then the homeserver isn't respecting the spec.
let langPolicy = policy[prefLang];
let langPolicy: LocalisedPolicy | undefined = policy[prefLang];
if (!langPolicy) langPolicy = policy["en"];
if (!langPolicy) {
// last resort
const firstLang = Object.keys(policy).find((e) => e !== "version");
langPolicy = policy[firstLang];
langPolicy = firstLang ? policy[firstLang] : undefined;
}
if (!langPolicy) throw new Error("Failed to find a policy to show the user");
@ -358,7 +358,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
return <Spinner />;
}
const checkboxes = [];
const checkboxes: JSX.Element[] = [];
let allChecked = true;
for (const policy of this.state.policies) {
const checked = this.state.toggledPolicies[policy.id];
@ -384,7 +384,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
);
}
let submitButton;
let submitButton: JSX.Element | undefined;
if (this.props.showContinue !== false) {
// XXX: button classes
submitButton = (
@ -462,7 +462,7 @@ export class EmailIdentityAuthEntry extends React.Component<
// We only have a session ID if the user has clicked the link in their email,
// so show a loading state instead of "an email has been sent to..." because
// that's confusing when you've already read that email.
if (this.props.inputs.emailAddress === undefined || this.props.stageState?.emailSid) {
if (this.props.inputs?.emailAddress === undefined || this.props.stageState?.emailSid) {
if (errorSection) {
return errorSection;
}
@ -549,13 +549,13 @@ interface IMsisdnAuthEntryProps extends IAuthEntryProps {
interface IMsisdnAuthEntryState {
token: string;
requestingToken: boolean;
errorText: string;
errorText: string | null;
}
export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsisdnAuthEntryState> {
public static LOGIN_TYPE = AuthType.Msisdn;
private submitUrl: string;
private submitUrl?: string;
private sid: string;
private msisdn: string;
@ -798,11 +798,13 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
public static PHASE_POSTAUTH = 2; // button to confirm SSO completed
private ssoUrl: string;
private popupWindow: Window;
private popupWindow: Window | null;
public constructor(props: ISSOAuthEntryProps) {
super(props);
if (!this.props.authSessionId) throw new Error("This UIA flow requires an authSessionId");
// We actually send the user through fallback auth so we don't have to
// deal with a redirect back to us, losing application context.
this.ssoUrl = props.matrixClient.getFallbackAuthUrl(this.props.loginType, this.props.authSessionId);
@ -858,10 +860,10 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
};
public render(): React.ReactNode {
let continueButton = null;
let continueButton: JSX.Element;
const cancelButton = (
<AccessibleButton
onClick={this.props.onCancel}
onClick={this.props.onCancel ?? null}
kind={this.props.continueKind ? this.props.continueKind + "_outline" : "primary_outline"}
>
{_t("Cancel")}
@ -909,7 +911,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
}
export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
private popupWindow: Window;
private popupWindow: Window | null;
private fallbackButton = createRef<HTMLButtonElement>();
public constructor(props: IAuthEntryProps) {
@ -927,18 +929,16 @@ export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
public componentWillUnmount(): void {
window.removeEventListener("message", this.onReceiveMessage);
if (this.popupWindow) {
this.popupWindow.close();
}
this.popupWindow?.close();
}
public focus = (): void => {
if (this.fallbackButton.current) {
this.fallbackButton.current.focus();
}
this.fallbackButton.current?.focus();
};
private onShowFallbackClick = (e: MouseEvent): void => {
if (!this.props.authSessionId) return;
e.preventDefault();
e.stopPropagation();

View File

@ -26,7 +26,7 @@ import LanguageDropdown from "../elements/LanguageDropdown";
function onChange(newLang: string): void {
if (getCurrentLanguage() !== newLang) {
SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLang);
PlatformPeg.get().reload();
PlatformPeg.get()?.reload();
}
}

View File

@ -20,15 +20,16 @@ import Field, { IInputProps } from "../elements/Field";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import { _t, _td } from "../../../languageHandler";
interface IProps extends Omit<IInputProps, "onValidate"> {
interface IProps extends Omit<IInputProps, "onValidate" | "label"> {
id?: string;
fieldRef?: RefCallback<Field> | RefObject<Field>;
autoComplete?: string;
value: string;
password: string; // The password we're confirming
labelRequired?: string;
labelInvalid?: string;
label: string;
labelRequired: string;
labelInvalid: string;
onChange(ev: React.FormEvent<HTMLElement>): void;
onValidate?(result: IValidationResult): void;

View File

@ -31,10 +31,10 @@ interface IProps extends Omit<IInputProps, "onValidate"> {
value: string;
fieldRef?: RefCallback<Field> | RefObject<Field>;
label?: string;
labelEnterPassword?: string;
labelStrongPassword?: string;
labelAllowedButUnsafe?: string;
label: string;
labelEnterPassword: string;
labelStrongPassword: string;
labelAllowedButUnsafe: string;
onChange(ev: React.FormEvent<HTMLElement>): void;
onValidate?(result: IValidationResult): void;
@ -48,12 +48,12 @@ class PassphraseField extends PureComponent<IProps> {
labelAllowedButUnsafe: _td("Password is allowed, but unsafe"),
};
public readonly validate = withValidation<this, zxcvbn.ZXCVBNResult>({
public readonly validate = withValidation<this, zxcvbn.ZXCVBNResult | null>({
description: function (complexity) {
const score = complexity ? complexity.score : 0;
return <progress className="mx_PassphraseField_progress" max={4} value={score} />;
},
deriveData: async ({ value }): Promise<zxcvbn.ZXCVBNResult> => {
deriveData: async ({ value }): Promise<zxcvbn.ZXCVBNResult | null> => {
if (!value) return null;
const { scorePassword } = await import("../../../utils/PasswordScorer");
return scorePassword(value);
@ -67,7 +67,7 @@ class PassphraseField extends PureComponent<IProps> {
{
key: "complexity",
test: async function ({ value }, complexity): Promise<boolean> {
if (!value) {
if (!value || !complexity) {
return false;
}
const safe = complexity.score >= this.props.minScore;
@ -78,7 +78,7 @@ class PassphraseField extends PureComponent<IProps> {
// Unsafe passwords that are valid are only possible through a
// configuration flag. We'll print some helper text to signal
// to the user that their password is allowed, but unsafe.
if (complexity.score >= this.props.minScore) {
if (complexity && complexity.score >= this.props.minScore) {
return _t(this.props.labelStrongPassword);
}
return _t(this.props.labelAllowedButUnsafe);

View File

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { BaseSyntheticEvent } from "react";
import React, { BaseSyntheticEvent, ReactNode } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixError } from "matrix-js-sdk/src/matrix";
@ -82,7 +82,7 @@ interface IState {
// Field error codes by field ID
fieldValid: Partial<Record<RegistrationField, boolean>>;
// The ISO2 country code selected in the phone number entry
phoneCountry: string;
phoneCountry?: string;
username: string;
email: string;
phoneNumber: string;
@ -95,11 +95,11 @@ interface IState {
* A pure UI component which displays a registration form.
*/
export default class RegistrationForm extends React.PureComponent<IProps, IState> {
private [RegistrationField.Email]: Field;
private [RegistrationField.Password]: Field;
private [RegistrationField.PasswordConfirm]: Field;
private [RegistrationField.Username]: Field;
private [RegistrationField.PhoneNumber]: Field;
private [RegistrationField.Email]: Field | null;
private [RegistrationField.Password]: Field | null;
private [RegistrationField.PasswordConfirm]: Field | null;
private [RegistrationField.Username]: Field | null;
private [RegistrationField.PhoneNumber]: Field | null;
public static defaultProps = {
onValidationChange: logger.error,
@ -117,7 +117,6 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
phoneNumber: this.props.defaultPhoneNumber || "",
password: this.props.defaultPassword || "",
passwordConfirm: this.props.defaultPassword || "",
passwordComplexity: null,
};
}
@ -138,7 +137,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
if (this.showEmail()) {
Modal.createDialog(RegistrationEmailPromptDialog, {
onFinished: async (confirmed: boolean, email?: string): Promise<void> => {
if (confirmed) {
if (confirmed && email !== undefined) {
this.setState(
{
email,
@ -265,7 +264,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
};
private onEmailValidate = (result: IValidationResult): void => {
this.markFieldValid(RegistrationField.Email, result.valid);
this.markFieldValid(RegistrationField.Email, !!result.valid);
};
private validateEmailRules = withValidation({
@ -294,7 +293,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
};
private onPasswordValidate = (result: IValidationResult): void => {
this.markFieldValid(RegistrationField.Password, result.valid);
this.markFieldValid(RegistrationField.Password, !!result.valid);
};
private onPasswordConfirmChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
@ -304,7 +303,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
};
private onPasswordConfirmValidate = (result: IValidationResult): void => {
this.markFieldValid(RegistrationField.PasswordConfirm, result.valid);
this.markFieldValid(RegistrationField.PasswordConfirm, !!result.valid);
};
private onPhoneCountryChange = (newVal: PhoneNumberCountryDefinition): void => {
@ -321,7 +320,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
private onPhoneNumberValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.validatePhoneNumberRules(fieldState);
this.markFieldValid(RegistrationField.PhoneNumber, result.valid);
this.markFieldValid(RegistrationField.PhoneNumber, !!result.valid);
return result;
};
@ -352,14 +351,14 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
private onUsernameValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.validateUsernameRules(fieldState);
this.markFieldValid(RegistrationField.Username, result.valid);
this.markFieldValid(RegistrationField.Username, !!result.valid);
return result;
};
private validateUsernameRules = withValidation<this, UsernameAvailableStatus>({
description: (_, results) => {
// omit the description if the only failing result is the `available` one as it makes no sense for it.
if (results.every(({ key, valid }) => key === "available" || valid)) return;
if (results.every(({ key, valid }) => key === "available" || valid)) return null;
return _t("Use lowercase letters, numbers, dashes and underscores only");
},
hideDescriptionIfValid: true,
@ -448,7 +447,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
return true;
}
private renderEmail(): JSX.Element {
private renderEmail(): ReactNode {
if (!this.showEmail()) {
return null;
}
@ -492,7 +491,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
);
}
public renderPhoneNumber(): JSX.Element {
public renderPhoneNumber(): ReactNode {
if (!this.showPhoneNumber()) {
return null;
}
@ -518,7 +517,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
);
}
public renderUsername(): JSX.Element {
public renderUsername(): ReactNode {
return (
<Field
id="mx_RegistrationForm_username"
@ -534,12 +533,12 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
);
}
public render(): React.ReactNode {
public render(): ReactNode {
const registerButton = (
<input className="mx_Login_submit" type="submit" value={_t("Register")} disabled={!this.props.canSubmit} />
);
let emailHelperText = null;
let emailHelperText: JSX.Element | undefined;
if (this.showEmail()) {
if (this.showPhoneNumber()) {
emailHelperText = (

View File

@ -34,7 +34,7 @@ interface IProps {}
export default class Welcome extends React.PureComponent<IProps> {
public render(): React.ReactNode {
const pagesConfig = SdkConfig.getObject("embedded_pages");
let pageUrl!: string;
let pageUrl: string | undefined;
if (pagesConfig) {
pageUrl = pagesConfig.get("welcome_url");
}

View File

@ -44,15 +44,15 @@ interface IProps {
interface IState {
shouldErase: boolean;
errStr: string;
errStr: string | null;
authData: any; // for UIA
authEnabled: boolean; // see usages for information
// A few strings that are passed to InteractiveAuth for design or are displayed
// next to the InteractiveAuth component.
bodyText: string;
continueText: string;
continueKind: string;
bodyText?: string;
continueText?: string;
continueKind?: string;
}
export default class DeactivateAccountDialog extends React.Component<IProps, IState> {
@ -64,12 +64,6 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
errStr: null,
authData: null, // for UIA
authEnabled: true, // see usages for information
// A few strings that are passed to InteractiveAuth for design or are displayed
// next to the InteractiveAuth component.
bodyText: null,
continueText: null,
continueKind: null,
};
this.initAuth(/* shouldErase= */ false);
@ -101,14 +95,16 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
};
const aesthetics = DEACTIVATE_AESTHETICS[stage];
let bodyText = null;
let continueText = null;
let continueKind = null;
let bodyText: string | undefined;
let continueText: string | undefined;
let continueKind: string | undefined;
if (aesthetics) {
const phaseAesthetics = aesthetics[phase];
if (phaseAesthetics?.body) bodyText = phaseAesthetics.body;
if (phaseAesthetics?.continueText) continueText = phaseAesthetics.continueText;
if (phaseAesthetics?.continueKind) continueKind = phaseAesthetics.continueKind;
if (phaseAesthetics) {
if (phaseAesthetics.body) bodyText = phaseAesthetics.body;
if (phaseAesthetics.continueText) continueText = phaseAesthetics.continueText;
if (phaseAesthetics.continueKind) continueKind = phaseAesthetics.continueKind;
}
}
this.setState({ bodyText, continueText, continueKind });
};
@ -183,7 +179,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
}
public render(): React.ReactNode {
let error = null;
let error: JSX.Element | undefined;
if (this.state.errStr) {
error = <div className="error">{this.state.errStr}</div>;
}

View File

@ -76,7 +76,7 @@ export interface InteractiveAuthDialogProps {
}
interface IState {
authError: Error;
authError: Error | null;
// See _onUpdateStagePhase()
uiaStage: AuthType | null;
@ -147,16 +147,22 @@ export default class InteractiveAuthDialog extends React.Component<InteractiveAu
let title = this.state.authError ? "Error" : this.props.title || _t("Authentication");
let body = this.state.authError ? null : this.props.body;
let continueText = null;
let continueKind = null;
let continueText: string | undefined;
let continueKind: string | undefined;
const dialogAesthetics = this.props.aestheticsForStagePhases || this.getDefaultDialogAesthetics();
if (!this.state.authError && dialogAesthetics) {
if (dialogAesthetics[this.state.uiaStage]) {
const aesthetics = dialogAesthetics[this.state.uiaStage][this.state.uiaStagePhase];
if (aesthetics?.title) title = aesthetics.title;
if (aesthetics?.body) body = aesthetics.body;
if (aesthetics?.continueText) continueText = aesthetics.continueText;
if (aesthetics?.continueKind) continueKind = aesthetics.continueKind;
if (
this.state.uiaStage !== null &&
this.state.uiaStagePhase !== null &&
dialogAesthetics[this.state.uiaStage]
) {
const aesthetics = dialogAesthetics[this.state.uiaStage]![this.state.uiaStagePhase];
if (aesthetics) {
if (aesthetics.title) title = aesthetics.title;
if (aesthetics.body) body = aesthetics.body;
if (aesthetics.continueText) continueText = aesthetics.continueText;
if (aesthetics.continueKind) continueKind = aesthetics.continueKind;
}
}
}

View File

@ -24,7 +24,8 @@ import DialogButtons from "../elements/DialogButtons";
import EmailField from "../auth/EmailField";
interface IProps {
onFinished(continued: boolean, email?: string): void;
onFinished(continued: false, email?: undefined): void;
onFinished(continued: true, email: string): void;
}
const RegistrationEmailPromptDialog: React.FC<IProps> = ({ onFinished }) => {

View File

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ReactNode } from "react";
import classNames from "classnames";
type Data = Pick<IFieldState, "value" | "allowEmpty">;
@ -31,13 +31,13 @@ interface IRule<T, D = undefined> {
final?: boolean;
skip?(this: T, data: Data, derivedData: D): boolean;
test(this: T, data: Data, derivedData: D): boolean | Promise<boolean>;
valid?(this: T, derivedData: D): string;
invalid?(this: T, derivedData: D): string;
valid?(this: T, derivedData: D): string | null;
invalid?(this: T, derivedData: D): string | null;
}
interface IArgs<T, D = void> {
rules: IRule<T, D>[];
description?(this: T, derivedData: D, results: IResult[]): React.ReactChild;
description?(this: T, derivedData: D, results: IResult[]): ReactNode;
hideDescriptionIfValid?: boolean;
deriveData?(data: Data): Promise<D>;
}

View File

@ -34,12 +34,12 @@ import { _t, _td, Tags, TranslatedString } from "../languageHandler";
* @returns {*} Translated string or react component
*/
export function messageForResourceLimitError(
limitType: string,
limitType: string | undefined,
adminContact: string | undefined,
strings: Record<string, string>,
extraTranslations?: Tags,
): TranslatedString {
let errString = strings[limitType];
let errString = limitType ? strings[limitType] : undefined;
if (errString === undefined) errString = strings[""];
const linkSub = (sub: string): ReactNode => {