Apply `strictNullChecks` to `src/components/views/auth/*` (#10299
* Apply `strictNullChecks` to src/components/views/auth/* * Iterate PRpull/28788/head^2
parent
c79eff2292
commit
32aa18ff2e
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
Loading…
Reference in New Issue