From 2e9cacdeb1d57537dc08dee931d8e4c87d837c59 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 10:22:43 +0200
Subject: [PATCH 01/23] Migrate JumpToBottomButton to TypeScript

---
 .../{JumpToBottomButton.js => JumpToBottomButton.tsx} | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)
 rename src/components/views/rooms/{JumpToBottomButton.js => JumpToBottomButton.tsx} (83%)

diff --git a/src/components/views/rooms/JumpToBottomButton.js b/src/components/views/rooms/JumpToBottomButton.tsx
similarity index 83%
rename from src/components/views/rooms/JumpToBottomButton.js
rename to src/components/views/rooms/JumpToBottomButton.tsx
index d2e2a391a6..0b680d093d 100644
--- a/src/components/views/rooms/JumpToBottomButton.js
+++ b/src/components/views/rooms/JumpToBottomButton.tsx
@@ -14,11 +14,18 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
+import React from "react";
 import { _t } from '../../../languageHandler';
 import AccessibleButton from '../elements/AccessibleButton';
 import classNames from 'classnames';
 
-export default (props) => {
+interface IProps {
+    numUnreadMessages: number;
+    highlight: boolean;
+    onScrollToBottomClick: (e: React.MouseEvent) => void;
+}
+
+const JumpToBottomButton: React.FC<IProps> = (props) => {
     const className = classNames({
         'mx_JumpToBottomButton': true,
         'mx_JumpToBottomButton_highlight': props.highlight,
@@ -36,3 +43,5 @@ export default (props) => {
         { badge }
     </div>);
 };
+
+export default JumpToBottomButton;

From e9e6269da7487c7b3456b149d757e08760db6136 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 10:31:18 +0200
Subject: [PATCH 02/23] Migrat ReadReceiptMarker to TypeScript

---
 src/components/views/rooms/EventTile.tsx      |   1 +
 ...ReceiptMarker.js => ReadReceiptMarker.tsx} | 114 ++++++++++--------
 2 files changed, 64 insertions(+), 51 deletions(-)
 rename src/components/views/rooms/{ReadReceiptMarker.js => ReadReceiptMarker.tsx} (74%)

diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index 301e33ec42..c5fbb99ede 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -240,6 +240,7 @@ interface IProps {
     // opaque readreceipt info for each userId; used by ReadReceiptMarker
     // to manage its animations. Should be an empty object when the room
     // first loads
+    // TODO: Proper typing for RR info
     readReceiptMap?: any;
 
     // A function which is used to check if the parent panel is being
diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.tsx
similarity index 74%
rename from src/components/views/rooms/ReadReceiptMarker.js
rename to src/components/views/rooms/ReadReceiptMarker.tsx
index c9688b4d29..11e7563f11 100644
--- a/src/components/views/rooms/ReadReceiptMarker.js
+++ b/src/components/views/rooms/ReadReceiptMarker.tsx
@@ -16,7 +16,8 @@ limitations under the License.
 */
 
 import React, { createRef } from 'react';
-import PropTypes from 'prop-types';
+import { RoomMember } from 'matrix-js-sdk/src';
+
 import { _t } from '../../../languageHandler';
 import { formatDate } from '../../../DateUtils';
 import NodeAnimator from "../../../NodeAnimator";
@@ -24,53 +25,64 @@ import * as sdk from "../../../index";
 import { toPx } from "../../../utils/units";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 
+interface IProps {
+    // the RoomMember to show the RR for
+    member?: RoomMember;
+    // userId to fallback the avatar to
+    // if the member hasn't been loaded yet
+    fallbackUserId: string;
+
+    // number of pixels to offset the avatar from the right of its parent;
+    // typically a negative value.
+    leftOffset?: number;
+
+    // true to hide the avatar (it will still be animated)
+    hidden?: boolean;
+
+    // don't animate this RR into position
+    suppressAnimation?: boolean;
+
+    // an opaque object for storing information about this user's RR in
+    // this room
+    // TODO: proper typing for RR info
+    readReceiptInfo: any;
+
+    // A function which is used to check if the parent panel is being
+    // unmounted, to avoid unnecessary work. Should return true if we
+    // are being unmounted.
+    checkUnmounting?: () => boolean;
+
+    // callback for clicks on this RR
+    onClick?: (e: React.MouseEvent) => void;
+
+    // Timestamp when the receipt was read
+    timestamp?: number;
+
+    // True to show twelve hour format, false otherwise
+    showTwelveHour?: boolean;
+}
+
+interface IState {
+    suppressDisplay: boolean;
+    startStyles?: IReadReceiptMarkerStyle[];
+}
+
+interface IReadReceiptMarkerStyle {
+    top: number;
+    left: number;
+}
+
 @replaceableComponent("views.rooms.ReadReceiptMarker")
-export default class ReadReceiptMarker extends React.PureComponent {
-    static propTypes = {
-        // the RoomMember to show the RR for
-        member: PropTypes.object,
-        // userId to fallback the avatar to
-        // if the member hasn't been loaded yet
-        fallbackUserId: PropTypes.string.isRequired,
-
-        // number of pixels to offset the avatar from the right of its parent;
-        // typically a negative value.
-        leftOffset: PropTypes.number,
-
-        // true to hide the avatar (it will still be animated)
-        hidden: PropTypes.bool,
-
-        // don't animate this RR into position
-        suppressAnimation: PropTypes.bool,
-
-        // an opaque object for storing information about this user's RR in
-        // this room
-        readReceiptInfo: PropTypes.object,
-
-        // A function which is used to check if the parent panel is being
-        // unmounted, to avoid unnecessary work. Should return true if we
-        // are being unmounted.
-        checkUnmounting: PropTypes.func,
-
-        // callback for clicks on this RR
-        onClick: PropTypes.func,
-
-        // Timestamp when the receipt was read
-        timestamp: PropTypes.number,
-
-        // True to show twelve hour format, false otherwise
-        showTwelveHour: PropTypes.bool,
-    };
+export default class ReadReceiptMarker extends React.PureComponent<IProps, IState> {
+    private avatar: React.RefObject<HTMLDivElement> = createRef();
 
     static defaultProps = {
         leftOffset: 0,
     };
 
-    constructor(props) {
+    constructor(props: IProps) {
         super(props);
 
-        this._avatar = createRef();
-
         this.state = {
             // if we are going to animate the RR, we don't show it on first render,
             // and instead just add a placeholder to the DOM; once we've been
@@ -80,7 +92,7 @@ export default class ReadReceiptMarker extends React.PureComponent {
         };
     }
 
-    componentWillUnmount() {
+    public componentWillUnmount(): void {
         // before we remove the rr, store its location in the map, so that if
         // it reappears, it can be animated from the right place.
         const rrInfo = this.props.readReceiptInfo;
@@ -95,29 +107,29 @@ export default class ReadReceiptMarker extends React.PureComponent {
             return;
         }
 
-        const avatarNode = this._avatar.current;
+        const avatarNode = this.avatar.current;
         rrInfo.top = avatarNode.offsetTop;
         rrInfo.left = avatarNode.offsetLeft;
         rrInfo.parent = avatarNode.offsetParent;
     }
 
-    componentDidMount() {
+    public componentDidMount(): void {
         if (!this.state.suppressDisplay) {
             // we've already done our display - nothing more to do.
             return;
         }
-        this._animateMarker();
+        this.animateMarker();
     }
 
-    componentDidUpdate(prevProps) {
+    public componentDidUpdate(prevProps: IProps): void {
         const differentLeftOffset = prevProps.leftOffset !== this.props.leftOffset;
         const visibilityChanged = prevProps.hidden !== this.props.hidden;
         if (differentLeftOffset || visibilityChanged) {
-            this._animateMarker();
+            this.animateMarker();
         }
     }
 
-    _animateMarker() {
+    private animateMarker(): void {
         // treat new RRs as though they were off the top of the screen
         let oldTop = -15;
 
@@ -126,7 +138,7 @@ export default class ReadReceiptMarker extends React.PureComponent {
             oldTop = oldInfo.top + oldInfo.parent.getBoundingClientRect().top;
         }
 
-        const newElement = this._avatar.current;
+        const newElement = this.avatar.current;
         let startTopOffset;
         if (!newElement.offsetParent) {
             // this seems to happen sometimes for reasons I don't understand
@@ -156,10 +168,10 @@ export default class ReadReceiptMarker extends React.PureComponent {
         });
     }
 
-    render() {
+    public render(): JSX.Element {
         const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
         if (this.state.suppressDisplay) {
-            return <div ref={this._avatar} />;
+            return <div ref={this.avatar} />;
         }
 
         const style = {
@@ -198,7 +210,7 @@ export default class ReadReceiptMarker extends React.PureComponent {
                     style={style}
                     title={title}
                     onClick={this.props.onClick}
-                    inputRef={this._avatar}
+                    inputRef={this.avatar}
                 />
             </NodeAnimator>
         );

From 7290a65924b830868ecb7c0bf712d2a1462514c3 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 10:36:04 +0200
Subject: [PATCH 03/23] Migrate RoomDetailList to TypeScript

---
 .../{RoomDetailList.js => RoomDetailList.tsx} | 27 +++++++++----------
 1 file changed, 13 insertions(+), 14 deletions(-)
 rename src/components/views/rooms/{RoomDetailList.js => RoomDetailList.tsx} (81%)

diff --git a/src/components/views/rooms/RoomDetailList.js b/src/components/views/rooms/RoomDetailList.tsx
similarity index 81%
rename from src/components/views/rooms/RoomDetailList.js
rename to src/components/views/rooms/RoomDetailList.tsx
index bf2f5418c9..ee7383d7c7 100644
--- a/src/components/views/rooms/RoomDetailList.js
+++ b/src/components/views/rooms/RoomDetailList.tsx
@@ -14,24 +14,23 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
+import React from 'react';
+import { Room } from 'matrix-js-sdk/src';
+import classNames from 'classnames';
 import * as sdk from '../../../index';
 import dis from '../../../dispatcher/dispatcher';
-import React from 'react';
 import { _t } from '../../../languageHandler';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
 
-import { roomShape } from './RoomDetailRow';
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 
-@replaceableComponent("views.rooms.RoomDetailList")
-export default class RoomDetailList extends React.Component {
-    static propTypes = {
-        rooms: PropTypes.arrayOf(roomShape),
-        className: PropTypes.string,
-    };
+interface IProps {
+    rooms?: Room[];
+    className?: string;
+}
 
-    getRows() {
+@replaceableComponent("views.rooms.RoomDetailList")
+export default class RoomDetailList extends React.Component<IProps> {
+    public getRows(): JSX.Element[] {
         if (!this.props.rooms) return [];
 
         const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow');
@@ -40,15 +39,15 @@ export default class RoomDetailList extends React.Component {
         });
     }
 
-    onDetailsClick = (ev, room) => {
+    public onDetailsClick = (ev: React.MouseEvent, room: Room): void => {
         dis.dispatch({
             action: 'view_room',
             room_id: room.roomId,
-            room_alias: room.canonicalAlias || (room.aliases || [])[0],
+            room_alias: room.getCanonicalAlias() || (room.getAltAliases() || [])[0],
         });
     };
 
-    render() {
+    public render(): JSX.Element {
         const rows = this.getRows();
         let rooms;
         if (rows.length === 0) {

From 7e4c88f6ba11d610e4930daf8eab0cc3f1698738 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 10:46:38 +0200
Subject: [PATCH 04/23] Migrate RoomUpgradeWarningBar to TypeScript

---
 ...arningBar.js => RoomUpgradeWarningBar.tsx} | 38 +++++++++----------
 1 file changed, 18 insertions(+), 20 deletions(-)
 rename src/components/views/rooms/{RoomUpgradeWarningBar.js => RoomUpgradeWarningBar.tsx} (88%)

diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.js b/src/components/views/rooms/RoomUpgradeWarningBar.tsx
similarity index 88%
rename from src/components/views/rooms/RoomUpgradeWarningBar.js
rename to src/components/views/rooms/RoomUpgradeWarningBar.tsx
index 384845cdf9..6706e248e0 100644
--- a/src/components/views/rooms/RoomUpgradeWarningBar.js
+++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2018-2020 New Vector Ltd
+Copyright 2018-2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@ limitations under the License.
 */
 
 import React from 'react';
-import PropTypes from 'prop-types';
+import { MatrixEvent, Room, RoomState } from 'matrix-js-sdk/src';
 import * as sdk from '../../../index';
 import Modal from '../../../Modal';
 
@@ -23,33 +23,31 @@ import { _t } from '../../../languageHandler';
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 
+interface IProps {
+    room: Room;
+}
+
+interface IState {
+    upgraded?: boolean;
+}
+
 @replaceableComponent("views.rooms.RoomUpgradeWarningBar")
-export default class RoomUpgradeWarningBar extends React.PureComponent {
-    static propTypes = {
-        room: PropTypes.object.isRequired,
-        recommendation: PropTypes.object.isRequired,
-    };
-
-    constructor(props) {
-        super(props);
-        this.state = {};
-    }
-
-    componentDidMount() {
+export default class RoomUpgradeWarningBar extends React.PureComponent<IProps, IState> {
+    public componentDidMount(): void {
         const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", "");
         this.setState({ upgraded: tombstone && tombstone.getContent().replacement_room });
 
-        MatrixClientPeg.get().on("RoomState.events", this._onStateEvents);
+        MatrixClientPeg.get().on("RoomState.events", this.onStateEvents);
     }
 
-    componentWillUnmount() {
+    public componentWillUnmount(): void {
         const cli = MatrixClientPeg.get();
         if (cli) {
-            cli.removeListener("RoomState.events", this._onStateEvents);
+            cli.removeListener("RoomState.events", this.onStateEvents);
         }
     }
 
-    _onStateEvents = (event, state) => {
+    private onStateEvents = (event: MatrixEvent, state: RoomState): void => {
         if (!this.props.room || event.getRoomId() !== this.props.room.roomId) {
             return;
         }
@@ -60,12 +58,12 @@ export default class RoomUpgradeWarningBar extends React.PureComponent {
         this.setState({ upgraded: tombstone && tombstone.getContent().replacement_room });
     };
 
-    onUpgradeClick = () => {
+    private onUpgradeClick = (): void => {
         const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog');
         Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, { room: this.props.room });
     };
 
-    render() {
+    public render(): JSX.Element {
         const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
 
         let doUpgradeWarnings = (

From eb120901aea7c1fb5b54bb9ceb8cf7516a7498d7 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 10:48:30 +0200
Subject: [PATCH 05/23] Migrate SimpleRoomHeader to TypeScript

---
 ...mpleRoomHeader.js => SimpleRoomHeader.tsx} | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)
 rename src/components/views/rooms/{SimpleRoomHeader.js => SimpleRoomHeader.tsx} (84%)

diff --git a/src/components/views/rooms/SimpleRoomHeader.js b/src/components/views/rooms/SimpleRoomHeader.tsx
similarity index 84%
rename from src/components/views/rooms/SimpleRoomHeader.js
rename to src/components/views/rooms/SimpleRoomHeader.tsx
index a2b5566e39..b81e906559 100644
--- a/src/components/views/rooms/SimpleRoomHeader.js
+++ b/src/components/views/rooms/SimpleRoomHeader.tsx
@@ -1,5 +1,6 @@
 /*
 Copyright 2016 OpenMarket Ltd
+Copyright 2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -15,23 +16,21 @@ limitations under the License.
 */
 
 import React from 'react';
-import PropTypes from 'prop-types';
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 
+interface IProps {
+    title?: string;
+    // `src` to an image. Optional.
+    icon?: string;
+}
+
 /*
  * A stripped-down room header used for things like the user settings
  * and room directory.
  */
 @replaceableComponent("views.rooms.SimpleRoomHeader")
-export default class SimpleRoomHeader extends React.Component {
-    static propTypes = {
-        title: PropTypes.string,
-
-        // `src` to an image. Optional.
-        icon: PropTypes.string,
-    };
-
-    render() {
+export default class SimpleRoomHeader extends React.PureComponent<IProps> {
+    public render(): JSX.Element {
         let icon;
         if (this.props.icon) {
             icon = <img

From c56d26731658777d6618ffbb22e80e1234064406 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 10:51:08 +0200
Subject: [PATCH 06/23] Migrate TopUnreadMessagesBar to TypeScript

---
 ...dMessagesBar.js => TopUnreadMessagesBar.tsx} | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)
 rename src/components/views/rooms/{TopUnreadMessagesBar.js => TopUnreadMessagesBar.tsx} (84%)

diff --git a/src/components/views/rooms/TopUnreadMessagesBar.js b/src/components/views/rooms/TopUnreadMessagesBar.tsx
similarity index 84%
rename from src/components/views/rooms/TopUnreadMessagesBar.js
rename to src/components/views/rooms/TopUnreadMessagesBar.tsx
index d2a3e3a303..01797299cf 100644
--- a/src/components/views/rooms/TopUnreadMessagesBar.js
+++ b/src/components/views/rooms/TopUnreadMessagesBar.tsx
@@ -1,7 +1,7 @@
 /*
 Copyright 2016 OpenMarket Ltd
 Copyright 2017 Vector Creations Ltd
-Copyright 2019 New Vector Ltd
+Copyright 2019-2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -17,19 +17,18 @@ limitations under the License.
 */
 
 import React from 'react';
-import PropTypes from 'prop-types';
 import { _t } from '../../../languageHandler';
 import AccessibleButton from '../elements/AccessibleButton';
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 
-@replaceableComponent("views.rooms.TopUnreadMessagesBar")
-export default class TopUnreadMessagesBar extends React.Component {
-    static propTypes = {
-        onScrollUpClick: PropTypes.func,
-        onCloseClick: PropTypes.func,
-    };
+interface IProps {
+    onScrollUpClick?: (e: React.MouseEvent) => void;
+    onCloseClick?: (e: React.MouseEvent) => void;
+}
 
-    render() {
+@replaceableComponent("views.rooms.TopUnreadMessagesBar")
+export default class TopUnreadMessagesBar extends React.PureComponent<IProps> {
+    public render(): JSX.Element {
         return (
             <div className="mx_TopUnreadMessagesBar">
                 <AccessibleButton

From 1f55158727edbe50cc71d51f3a27e89bbd7b90ff Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 10:53:53 +0200
Subject: [PATCH 07/23] Migrate AvatarSetting to TypeScript

---
 .../{AvatarSetting.js => AvatarSetting.tsx}   | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)
 rename src/components/views/settings/{AvatarSetting.js => AvatarSetting.tsx} (86%)

diff --git a/src/components/views/settings/AvatarSetting.js b/src/components/views/settings/AvatarSetting.tsx
similarity index 86%
rename from src/components/views/settings/AvatarSetting.js
rename to src/components/views/settings/AvatarSetting.tsx
index f22c4f1c85..806d0adb73 100644
--- a/src/components/views/settings/AvatarSetting.js
+++ b/src/components/views/settings/AvatarSetting.tsx
@@ -15,12 +15,19 @@ limitations under the License.
 */
 
 import React, { useState } from "react";
-import PropTypes from "prop-types";
 import { _t } from "../../../languageHandler";
 import AccessibleButton from "../elements/AccessibleButton";
 import classNames from "classnames";
 
-const AvatarSetting = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar }) => {
+interface IProps {
+    avatarUrl?: string;
+    avatarName: string; // name of user/room the avatar belongs to
+    uploadAvatar?: (e: React.MouseEvent) => void;
+    removeAvatar?: (e: React.MouseEvent) => void;
+    avatarAltText: string;
+}
+
+const AvatarSetting: React.FC<IProps> = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar }) => {
     const [isHovering, setIsHovering] = useState(false);
     const hoveringProps = {
         onMouseEnter: () => setIsHovering(true),
@@ -78,12 +85,4 @@ const AvatarSetting = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, rem
     </div>;
 };
 
-AvatarSetting.propTypes = {
-    avatarUrl: PropTypes.string,
-    avatarName: PropTypes.string.isRequired, // name of user/room the avatar belongs to
-    uploadAvatar: PropTypes.func,
-    removeAvatar: PropTypes.func,
-    avatarAltText: PropTypes.string.isRequired,
-};
-
 export default AvatarSetting;

From bedfbedff0ec3ffd8e081eef91951b76702ccf65 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 11:04:36 +0200
Subject: [PATCH 08/23] Migrate ChangeAvatar to TypeScript

---
 .../{ChangeAvatar.js => ChangeAvatar.tsx}     | 96 ++++++++++---------
 1 file changed, 52 insertions(+), 44 deletions(-)
 rename src/components/views/settings/{ChangeAvatar.js => ChangeAvatar.tsx} (74%)

diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.tsx
similarity index 74%
rename from src/components/views/settings/ChangeAvatar.js
rename to src/components/views/settings/ChangeAvatar.tsx
index c3a1544cdc..6394bad3de 100644
--- a/src/components/views/settings/ChangeAvatar.js
+++ b/src/components/views/settings/ChangeAvatar.tsx
@@ -1,5 +1,6 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -15,54 +16,62 @@ limitations under the License.
 */
 
 import React from 'react';
-import PropTypes from 'prop-types';
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import * as sdk from '../../../index';
 import { _t } from '../../../languageHandler';
 import Spinner from '../elements/Spinner';
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 import { mediaFromMxc } from "../../../customisations/Media";
+import { MatrixEvent, Room } from '../../../../../matrix-js-sdk/src';
+
+interface IProps {
+    initialAvatarUrl?: string;
+    room?: Room;
+    // if false, you need to call changeAvatar.onFileSelected yourself.
+    showUploadSection?: boolean;
+    width?: number;
+    height?: number;
+    className?: string;
+}
+
+interface IState {
+    avatarUrl?: string;
+    errorText?: string;
+    phase?: Phases;
+}
+
+enum Phases {
+    Display = "display",
+    Uploading = "uploading",
+    Error = "error",
+}
 
 @replaceableComponent("views.settings.ChangeAvatar")
-export default class ChangeAvatar extends React.Component {
-    static propTypes = {
-        initialAvatarUrl: PropTypes.string,
-        room: PropTypes.object,
-        // if false, you need to call changeAvatar.onFileSelected yourself.
-        showUploadSection: PropTypes.bool,
-        width: PropTypes.number,
-        height: PropTypes.number,
-        className: PropTypes.string,
-    };
-
-    static Phases = {
-        Display: "display",
-        Uploading: "uploading",
-        Error: "error",
-    };
-
-    static defaultProps = {
+export default class ChangeAvatar extends React.Component<IProps, IState> {
+    public static defaultProps = {
         showUploadSection: true,
         className: "",
         width: 80,
         height: 80,
     };
 
-    constructor(props) {
+    private avatarSet = false;
+
+    constructor(props: IProps) {
         super(props);
 
         this.state = {
             avatarUrl: this.props.initialAvatarUrl,
-            phase: ChangeAvatar.Phases.Display,
+            phase: Phases.Display,
         };
     }
 
-    componentDidMount() {
+    public componentDidMount(): void {
         MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
     }
 
     // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
-    UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase
+    public UNSAFE_componentWillReceiveProps(newProps): void { // eslint-disable-line camelcase
         if (this.avatarSet) {
             // don't clobber what the user has just set
             return;
@@ -72,13 +81,13 @@ export default class ChangeAvatar extends React.Component {
         });
     }
 
-    componentWillUnmount() {
+    public componentWillUnmount(): void {
         if (MatrixClientPeg.get()) {
             MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
         }
     }
 
-    onRoomStateEvents = (ev) => {
+    public onRoomStateEvents = (ev: MatrixEvent) => {
         if (!this.props.room) {
             return;
         }
@@ -94,18 +103,17 @@ export default class ChangeAvatar extends React.Component {
         }
     };
 
-    setAvatarFromFile(file) {
+    public setAvatarFromFile(file): Promise<{}> {
         let newUrl = null;
 
         this.setState({
-            phase: ChangeAvatar.Phases.Uploading,
+            phase: Phases.Uploading,
         });
-        const self = this;
-        const httpPromise = MatrixClientPeg.get().uploadContent(file).then(function(url) {
+        const httpPromise = MatrixClientPeg.get().uploadContent(file).then((url) => {
             newUrl = url;
-            if (self.props.room) {
+            if (this.props.room) {
                 return MatrixClientPeg.get().sendStateEvent(
-                    self.props.room.roomId,
+                    this.props.room.roomId,
                     'm.room.avatar',
                     { url: url },
                     '',
@@ -115,33 +123,33 @@ export default class ChangeAvatar extends React.Component {
             }
         });
 
-        httpPromise.then(function() {
-            self.setState({
-                phase: ChangeAvatar.Phases.Display,
+        httpPromise.then(() => {
+            this.setState({
+                phase: Phases.Display,
                 avatarUrl: mediaFromMxc(newUrl).srcHttp,
             });
-        }, function(error) {
-            self.setState({
-                phase: ChangeAvatar.Phases.Error,
+        }, () => {
+            this.setState({
+                phase: Phases.Error,
             });
-            self.onError(error);
+            this.onError();
         });
 
         return httpPromise;
     }
 
-    onFileSelected = (ev) => {
+    private onFileSelected = (ev: React.ChangeEvent<HTMLInputElement>) => {
         this.avatarSet = true;
         return this.setAvatarFromFile(ev.target.files[0]);
     };
 
-    onError = (error) => {
+    private onError = (): void => {
         this.setState({
             errorText: _t("Failed to upload profile picture!"),
         });
     };
 
-    render() {
+    public render(): JSX.Element {
         let avatarImg;
         // Having just set an avatar we just display that since it will take a little
         // time to propagate through to the RoomAvatar.
@@ -178,8 +186,8 @@ export default class ChangeAvatar extends React.Component {
         }
 
         switch (this.state.phase) {
-            case ChangeAvatar.Phases.Display:
-            case ChangeAvatar.Phases.Error:
+            case Phases.Display:
+            case Phases.Error:
                 return (
                     <div>
                         <div className={this.props.className}>
@@ -188,7 +196,7 @@ export default class ChangeAvatar extends React.Component {
                         { uploadSection }
                     </div>
                 );
-            case ChangeAvatar.Phases.Uploading:
+            case Phases.Uploading:
                 return (
                     <Spinner />
                 );

From 1e431057ff62f9511181c457652c2ed3b8aa826f Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 11:06:34 +0200
Subject: [PATCH 09/23] Migrate ChangeDisplayName to TypeScript

---
 ...hangeDisplayName.js => ChangeDisplayName.tsx} | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)
 rename src/components/views/settings/{ChangeDisplayName.js => ChangeDisplayName.tsx} (81%)

diff --git a/src/components/views/settings/ChangeDisplayName.js b/src/components/views/settings/ChangeDisplayName.tsx
similarity index 81%
rename from src/components/views/settings/ChangeDisplayName.js
rename to src/components/views/settings/ChangeDisplayName.tsx
index 2f336e18c6..09c9ecc23e 100644
--- a/src/components/views/settings/ChangeDisplayName.js
+++ b/src/components/views/settings/ChangeDisplayName.tsx
@@ -1,6 +1,6 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2018 New Vector Ltd
+Copyright 2018 - 2021 New Vector Ltd
 Copyright 2019 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
 
 @replaceableComponent("views.settings.ChangeDisplayName")
 export default class ChangeDisplayName extends React.Component {
-    _getDisplayName = async () => {
+    private getDisplayName = async (): Promise<string> => {
         const cli = MatrixClientPeg.get();
         try {
             const res = await cli.getProfileInfo(cli.getUserId());
@@ -34,21 +34,21 @@ export default class ChangeDisplayName extends React.Component {
         }
     };
 
-    _changeDisplayName = (newDisplayname) => {
+    private changeDisplayName = (newDisplayname: string): Promise<{}> => {
         const cli = MatrixClientPeg.get();
-        return cli.setDisplayName(newDisplayname).catch(function(e) {
-            throw new Error("Failed to set display name", e);
+        return cli.setDisplayName(newDisplayname).catch(function() {
+            throw new Error("Failed to set display name");
         });
     };
 
-    render() {
+    public render(): JSX.Element {
         const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer');
         return (
             <EditableTextContainer
-                getInitialValue={this._getDisplayName}
+                getInitialValue={this.getDisplayName}
                 placeholder={_t("No display name")}
                 blurToSubmit={true}
-                onSubmit={this._changeDisplayName} />
+                onSubmit={this.changeDisplayName} />
         );
     }
 }

From fb6a6370e7e37c3c5eef48c3208b3cb9d5915b22 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 11:17:19 +0200
Subject: [PATCH 10/23] Migrate DevicesPanel to TypeScript

---
 .../{DevicesPanel.js => DevicesPanel.tsx}     | 89 +++++++++----------
 1 file changed, 41 insertions(+), 48 deletions(-)
 rename src/components/views/settings/{DevicesPanel.js => DevicesPanel.tsx} (82%)

diff --git a/src/components/views/settings/DevicesPanel.js b/src/components/views/settings/DevicesPanel.tsx
similarity index 82%
rename from src/components/views/settings/DevicesPanel.js
rename to src/components/views/settings/DevicesPanel.tsx
index 0f052332ee..6fd7f90c8e 100644
--- a/src/components/views/settings/DevicesPanel.js
+++ b/src/components/views/settings/DevicesPanel.tsx
@@ -1,6 +1,7 @@
 /*
 Copyright 2016 OpenMarket Ltd
 Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -16,8 +17,8 @@ limitations under the License.
 */
 
 import React from 'react';
-import PropTypes from 'prop-types';
 import classNames from 'classnames';
+import { IMyDevice } from "matrix-js-sdk/src/client";
 
 import * as sdk from '../../../index';
 import { MatrixClientPeg } from '../../../MatrixClientPeg';
@@ -26,42 +27,37 @@ import Modal from '../../../Modal';
 import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 
+interface IProps {
+    className?: string;
+}
+
+interface IState {
+    devices: IMyDevice[];
+    deviceLoadError?: string;
+    selectedDevices?: string[];
+    deleting?: boolean;
+}
+
 @replaceableComponent("views.settings.DevicesPanel")
-export default class DevicesPanel extends React.Component {
-    constructor(props) {
-        super(props);
+export default class DevicesPanel extends React.Component<IProps, IState> {
+    private unmounted = false;
 
-        this.state = {
-            devices: undefined,
-            deviceLoadError: undefined,
-
-            selectedDevices: [],
-            deleting: false,
-        };
-
-        this._unmounted = false;
-
-        this._renderDevice = this._renderDevice.bind(this);
-        this._onDeviceSelectionToggled = this._onDeviceSelectionToggled.bind(this);
-        this._onDeleteClick = this._onDeleteClick.bind(this);
+    public componentDidMount(): void {
+        this.loadDevices();
     }
 
-    componentDidMount() {
-        this._loadDevices();
+    public componentWillUnmount(): void {
+        this.unmounted = true;
     }
 
-    componentWillUnmount() {
-        this._unmounted = true;
-    }
-
-    _loadDevices() {
+    private loadDevices(): void {
         MatrixClientPeg.get().getDevices().then(
             (resp) => {
-                if (this._unmounted) { return; }
+                if (this.unmounted) { return; }
                 this.setState({ devices: resp.devices || [] });
             },
             (error) => {
-                if (this._unmounted) { return; }
+                if (this.unmounted) { return; }
                 let errtxt;
                 if (error.httpStatus == 404) {
                     // 404 probably means the HS doesn't yet support the API.
@@ -79,7 +75,7 @@ export default class DevicesPanel extends React.Component {
      * compare two devices, sorting from most-recently-seen to least-recently-seen
      * (and then, for stability, by device id)
      */
-    _deviceCompare(a, b) {
+    private deviceCompare(a: IMyDevice, b: IMyDevice): number {
         // return < 0 if a comes before b, > 0 if a comes after b.
         const lastSeenDelta =
               (b.last_seen_ts || 0) - (a.last_seen_ts || 0);
@@ -91,8 +87,8 @@ export default class DevicesPanel extends React.Component {
         return (idA < idB) ? -1 : (idA > idB) ? 1 : 0;
     }
 
-    _onDeviceSelectionToggled(device) {
-        if (this._unmounted) { return; }
+    private onDeviceSelectionToggled = (device: IMyDevice): void => {
+        if (this.unmounted) { return; }
 
         const deviceId = device.device_id;
         this.setState((state, props) => {
@@ -108,15 +104,15 @@ export default class DevicesPanel extends React.Component {
 
             return { selectedDevices };
         });
-    }
+    };
 
-    _onDeleteClick() {
+    private onDeleteClick = (): void => {
         this.setState({
             deleting: true,
         });
 
-        this._makeDeleteRequest(null).catch((error) => {
-            if (this._unmounted) { return; }
+        this.makeDeleteRequest(null).catch((error) => {
+            if (this.unmounted) { return; }
             if (error.httpStatus !== 401 || !error.data || !error.data.flows) {
                 // doesn't look like an interactive-auth failure
                 throw error;
@@ -148,7 +144,7 @@ export default class DevicesPanel extends React.Component {
                 title: _t("Authentication"),
                 matrixClient: MatrixClientPeg.get(),
                 authData: error.data,
-                makeRequest: this._makeDeleteRequest.bind(this),
+                makeRequest: this.makeDeleteRequest.bind(this),
                 aestheticsForStagePhases: {
                     [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
                     [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
@@ -156,15 +152,16 @@ export default class DevicesPanel extends React.Component {
             });
         }).catch((e) => {
             console.error("Error deleting sessions", e);
-            if (this._unmounted) { return; }
+            if (this.unmounted) { return; }
         }).finally(() => {
             this.setState({
                 deleting: false,
             });
         });
-    }
+    };
 
-    _makeDeleteRequest(auth) {
+    // TODO: proper typing for auth
+    private makeDeleteRequest(auth?: any): Promise<any> {
         return MatrixClientPeg.get().deleteMultipleDevices(this.state.selectedDevices, auth).then(
             () => {
                 // Remove the deleted devices from `devices`, reset selection to []
@@ -178,17 +175,17 @@ export default class DevicesPanel extends React.Component {
         );
     }
 
-    _renderDevice(device) {
+    private renderDevice = (device: IMyDevice): JSX.Element => {
         const DevicesPanelEntry = sdk.getComponent('settings.DevicesPanelEntry');
         return <DevicesPanelEntry
             key={device.device_id}
             device={device}
             selected={this.state.selectedDevices.includes(device.device_id)}
-            onDeviceToggled={this._onDeviceSelectionToggled}
+            onDeviceToggled={this.onDeviceSelectionToggled}
         />;
-    }
+    };
 
-    render() {
+    public render(): JSX.Element {
         const Spinner = sdk.getComponent("elements.Spinner");
         const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
 
@@ -208,11 +205,11 @@ export default class DevicesPanel extends React.Component {
             return <Spinner className={classes} />;
         }
 
-        devices.sort(this._deviceCompare);
+        devices.sort(this.deviceCompare);
 
         const deleteButton = this.state.deleting ?
             <Spinner w={22} h={22} /> :
-            <AccessibleButton onClick={this._onDeleteClick} kind="danger_sm">
+            <AccessibleButton onClick={this.onDeleteClick} kind="danger_sm">
                 { _t("Delete %(count)s sessions", { count: this.state.selectedDevices.length }) }
             </AccessibleButton>;
 
@@ -227,12 +224,8 @@ export default class DevicesPanel extends React.Component {
                         { this.state.selectedDevices.length > 0 ? deleteButton : null }
                     </div>
                 </div>
-                { devices.map(this._renderDevice) }
+                { devices.map(this.renderDevice) }
             </div>
         );
     }
 }
-
-DevicesPanel.propTypes = {
-    className: PropTypes.string,
-};

From dfd986751ffdd810e5d54b7fa95d4470ebbca77e Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 11:21:59 +0200
Subject: [PATCH 11/23] Migrate DevicesPanelEntry to TypeScript

---
 ...cesPanelEntry.js => DevicesPanelEntry.tsx} | 46 ++++++++-----------
 1 file changed, 18 insertions(+), 28 deletions(-)
 rename src/components/views/settings/{DevicesPanelEntry.js => DevicesPanelEntry.tsx} (78%)

diff --git a/src/components/views/settings/DevicesPanelEntry.js b/src/components/views/settings/DevicesPanelEntry.tsx
similarity index 78%
rename from src/components/views/settings/DevicesPanelEntry.js
rename to src/components/views/settings/DevicesPanelEntry.tsx
index a5b674b8f6..d44147f591 100644
--- a/src/components/views/settings/DevicesPanelEntry.js
+++ b/src/components/views/settings/DevicesPanelEntry.tsx
@@ -1,5 +1,6 @@
 /*
 Copyright 2016 OpenMarket Ltd
+Copyright 2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -15,7 +16,7 @@ limitations under the License.
 */
 
 import React from 'react';
-import PropTypes from 'prop-types';
+import { IMyDevice } from 'matrix-js-sdk/src';
 
 import * as sdk from '../../../index';
 import { _t } from '../../../languageHandler';
@@ -24,21 +25,19 @@ import { formatDate } from '../../../DateUtils';
 import StyledCheckbox from '../elements/StyledCheckbox';
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 
+interface IProps {
+    device?: IMyDevice;
+    onDeviceToggled?: (device: IMyDevice) => void;
+    selected?: boolean;
+}
+
 @replaceableComponent("views.settings.DevicesPanelEntry")
-export default class DevicesPanelEntry extends React.Component {
-    constructor(props) {
-        super(props);
+export default class DevicesPanelEntry extends React.Component<IProps> {
+    public static defaultProps = {
+        onDeviceToggled: () => {},
+    };
 
-        this._unmounted = false;
-        this.onDeviceToggled = this.onDeviceToggled.bind(this);
-        this._onDisplayNameChanged = this._onDisplayNameChanged.bind(this);
-    }
-
-    componentWillUnmount() {
-        this._unmounted = true;
-    }
-
-    _onDisplayNameChanged(value) {
+    private onDisplayNameChanged = (value: string): Promise<{}> => {
         const device = this.props.device;
         return MatrixClientPeg.get().setDeviceDetails(device.device_id, {
             display_name: value,
@@ -46,13 +45,13 @@ export default class DevicesPanelEntry extends React.Component {
             console.error("Error setting session display name", e);
             throw new Error(_t("Failed to set display name"));
         });
-    }
+    };
 
-    onDeviceToggled() {
+    private onDeviceToggled = (): void => {
         this.props.onDeviceToggled(this.props.device);
-    }
+    };
 
-    render() {
+    public render(): JSX.Element {
         const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer');
 
         const device = this.props.device;
@@ -76,7 +75,7 @@ export default class DevicesPanelEntry extends React.Component {
                 </div>
                 <div className="mx_DevicesPanel_deviceName">
                     <EditableTextContainer initialValue={device.display_name}
-                        onSubmit={this._onDisplayNameChanged}
+                        onSubmit={this.onDisplayNameChanged}
                         placeholder={device.device_id}
                     />
                 </div>
@@ -90,12 +89,3 @@ export default class DevicesPanelEntry extends React.Component {
         );
     }
 }
-
-DevicesPanelEntry.propTypes = {
-    device: PropTypes.object.isRequired,
-    onDeviceToggled: PropTypes.func,
-};
-
-DevicesPanelEntry.defaultProps = {
-    onDeviceToggled: function() {},
-};

From 447beb829458104d2c29adb08751544a1433d4ea Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 11:27:17 +0200
Subject: [PATCH 12/23] Migrate IntegrationManager to TypeScript

---
 ...ationManager.js => IntegrationManager.tsx} | 61 ++++++++++---------
 1 file changed, 32 insertions(+), 29 deletions(-)
 rename src/components/views/settings/{IntegrationManager.js => IntegrationManager.tsx} (72%)

diff --git a/src/components/views/settings/IntegrationManager.js b/src/components/views/settings/IntegrationManager.tsx
similarity index 72%
rename from src/components/views/settings/IntegrationManager.js
rename to src/components/views/settings/IntegrationManager.tsx
index 9f2985df14..f43fb55004 100644
--- a/src/components/views/settings/IntegrationManager.js
+++ b/src/components/views/settings/IntegrationManager.tsx
@@ -1,6 +1,7 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
 Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -16,53 +17,55 @@ limitations under the License.
 */
 
 import React from 'react';
-import PropTypes from 'prop-types';
 import * as sdk from '../../../index';
 import { _t } from '../../../languageHandler';
 import dis from '../../../dispatcher/dispatcher';
 import { Key } from "../../../Keyboard";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { ActionPayload } from '../../../dispatcher/payloads';
+
+interface IProps {
+    // false to display an error saying that we couldn't connect to the integration manager
+    connected: boolean;
+
+    // true to display a loading spinner
+    loading: boolean;
+
+    // The source URL to load
+    url?: string;
+
+    // callback when the manager is dismissed
+    onFinished: () => void;
+}
+
+interface IState {
+    errored: boolean;
+}
 
 @replaceableComponent("views.settings.IntegrationManager")
-export default class IntegrationManager extends React.Component {
-    static propTypes = {
-        // false to display an error saying that we couldn't connect to the integration manager
-        connected: PropTypes.bool.isRequired,
+export default class IntegrationManager extends React.Component<IProps, IState> {
+    private dispatcherRef: string;
 
-        // true to display a loading spinner
-        loading: PropTypes.bool.isRequired,
-
-        // The source URL to load
-        url: PropTypes.string,
-
-        // callback when the manager is dismissed
-        onFinished: PropTypes.func.isRequired,
-    };
-
-    static defaultProps = {
+    public static defaultProps = {
         connected: true,
         loading: false,
     };
 
-    constructor(props) {
-        super(props);
+    public state = {
+        errored: false,
+    };
 
-        this.state = {
-            errored: false,
-        };
-    }
-
-    componentDidMount() {
+    public componentDidMount(): void {
         this.dispatcherRef = dis.register(this.onAction);
         document.addEventListener("keydown", this.onKeyDown);
     }
 
-    componentWillUnmount() {
+    public componentWillUnmount(): void {
         dis.unregister(this.dispatcherRef);
         document.removeEventListener("keydown", this.onKeyDown);
     }
 
-    onKeyDown = (ev) => {
+    private onKeyDown = (ev: KeyboardEvent): void => {
         if (ev.key === Key.ESCAPE) {
             ev.stopPropagation();
             ev.preventDefault();
@@ -70,17 +73,17 @@ export default class IntegrationManager extends React.Component {
         }
     };
 
-    onAction = (payload) => {
+    private onAction = (payload: ActionPayload): void => {
         if (payload.action === 'close_scalar') {
             this.props.onFinished();
         }
     };
 
-    onError = () => {
+    private onError = (): void => {
         this.setState({ errored: true });
     };
 
-    render() {
+    public render(): JSX.Element {
         if (this.props.loading) {
             const Spinner = sdk.getComponent("elements.Spinner");
             return (

From 2e1d5aa67bd1688544c26d2180c89a467c87807f Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Sat, 14 Aug 2021 11:36:12 +0200
Subject: [PATCH 13/23] Migrate ProfileSettings to TypeScript

---
 ...ProfileSettings.js => ProfileSettings.tsx} | 61 +++++++++++--------
 1 file changed, 37 insertions(+), 24 deletions(-)
 rename src/components/views/settings/{ProfileSettings.js => ProfileSettings.tsx} (83%)

diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.tsx
similarity index 83%
rename from src/components/views/settings/ProfileSettings.js
rename to src/components/views/settings/ProfileSettings.tsx
index d05fca983c..888ff8967b 100644
--- a/src/components/views/settings/ProfileSettings.js
+++ b/src/components/views/settings/ProfileSettings.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2019 New Vector Ltd
+Copyright 2019-2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -26,10 +26,25 @@ import ErrorDialog from "../dialogs/ErrorDialog";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 import { mediaFromMxc } from "../../../customisations/Media";
 
+interface IProps {
+
+}
+
+interface IState {
+    userId?: string;
+    originalDisplayName?: string;
+    displayName?: string;
+    originalAvatarUrl?: string;
+    avatarUrl?: string | ArrayBuffer;
+    avatarFile?: File;
+    enableProfileSave?: boolean;
+}
+
 @replaceableComponent("views.settings.ProfileSettings")
-export default class ProfileSettings extends React.Component {
-    constructor() {
-        super();
+export default class ProfileSettings extends React.Component<IProps, IState> {
+    private avatarUpload: React.RefObject<HTMLInputElement> = createRef();
+    constructor(props: IProps) {
+        super(props);
 
         const client = MatrixClientPeg.get();
         let avatarUrl = OwnProfileStore.instance.avatarMxc;
@@ -43,17 +58,15 @@ export default class ProfileSettings extends React.Component {
             avatarFile: null,
             enableProfileSave: false,
         };
-
-        this._avatarUpload = createRef();
     }
 
-    _uploadAvatar = () => {
-        this._avatarUpload.current.click();
+    private uploadAvatar = (): void => {
+        this.avatarUpload.current.click();
     };
 
-    _removeAvatar = () => {
+    private removeAvatar = (): void => {
         // clear file upload field so same file can be selected
-        this._avatarUpload.current.value = "";
+        this.avatarUpload.current.value = "";
         this.setState({
             avatarUrl: null,
             avatarFile: null,
@@ -61,7 +74,7 @@ export default class ProfileSettings extends React.Component {
         });
     };
 
-    _cancelProfileChanges = async (e) => {
+    private cancelProfileChanges = async (e: React.MouseEvent): Promise<void> => {
         e.stopPropagation();
         e.preventDefault();
 
@@ -74,7 +87,7 @@ export default class ProfileSettings extends React.Component {
         });
     };
 
-    _saveProfile = async (e) => {
+    private saveProfile = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
         e.stopPropagation();
         e.preventDefault();
 
@@ -82,7 +95,7 @@ export default class ProfileSettings extends React.Component {
         this.setState({ enableProfileSave: false });
 
         const client = MatrixClientPeg.get();
-        const newState = {};
+        const newState: IState = {};
 
         const displayName = this.state.displayName.trim();
         try {
@@ -115,14 +128,14 @@ export default class ProfileSettings extends React.Component {
         this.setState(newState);
     };
 
-    _onDisplayNameChanged = (e) => {
+    private onDisplayNameChanged = (e: React.ChangeEvent<HTMLInputElement>): void => {
         this.setState({
             displayName: e.target.value,
             enableProfileSave: true,
         });
     };
 
-    _onAvatarChanged = (e) => {
+    private onAvatarChanged = (e: React.ChangeEvent<HTMLInputElement>): void => {
         if (!e.target.files || !e.target.files.length) {
             this.setState({
                 avatarUrl: this.state.originalAvatarUrl,
@@ -144,7 +157,7 @@ export default class ProfileSettings extends React.Component {
         reader.readAsDataURL(file);
     };
 
-    render() {
+    public render(): JSX.Element {
         const hostingSignupLink = getHostingLink('user-settings');
         let hostingSignup = null;
         if (hostingSignupLink) {
@@ -165,16 +178,16 @@ export default class ProfileSettings extends React.Component {
         const AvatarSetting = sdk.getComponent('settings.AvatarSetting');
         return (
             <form
-                onSubmit={this._saveProfile}
+                onSubmit={this.saveProfile}
                 autoComplete="off"
                 noValidate={true}
                 className="mx_ProfileSettings_profileForm"
             >
                 <input
                     type="file"
-                    ref={this._avatarUpload}
+                    ref={this.avatarUpload}
                     className="mx_ProfileSettings_avatarUpload"
-                    onChange={this._onAvatarChanged}
+                    onChange={this.onAvatarChanged}
                     accept="image/*"
                 />
                 <div className="mx_ProfileSettings_profile">
@@ -185,7 +198,7 @@ export default class ProfileSettings extends React.Component {
                             type="text"
                             value={this.state.displayName}
                             autoComplete="off"
-                            onChange={this._onDisplayNameChanged}
+                            onChange={this.onDisplayNameChanged}
                         />
                         <p>
                             { this.state.userId }
@@ -196,19 +209,19 @@ export default class ProfileSettings extends React.Component {
                         avatarUrl={this.state.avatarUrl}
                         avatarName={this.state.displayName || this.state.userId}
                         avatarAltText={_t("Profile picture")}
-                        uploadAvatar={this._uploadAvatar}
-                        removeAvatar={this._removeAvatar} />
+                        uploadAvatar={this.uploadAvatar}
+                        removeAvatar={this.removeAvatar} />
                 </div>
                 <div className="mx_ProfileSettings_buttons">
                     <AccessibleButton
-                        onClick={this._cancelProfileChanges}
+                        onClick={this.cancelProfileChanges}
                         kind="link"
                         disabled={!this.state.enableProfileSave}
                     >
                         { _t("Cancel") }
                     </AccessibleButton>
                     <AccessibleButton
-                        onClick={this._saveProfile}
+                        onClick={this.saveProfile}
                         kind="primary"
                         disabled={!this.state.enableProfileSave}
                     >

From 800b3f1424d1aa24e6fa5663968fb6b65dc521ab Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Mon, 16 Aug 2021 09:16:02 +0100
Subject: [PATCH 14/23] Fix linter

---
 src/components/structures/RoomView.tsx         | 3 +--
 src/components/views/settings/ChangeAvatar.tsx | 3 ++-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index 474b99262d..31aa8d50fa 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -1867,7 +1867,7 @@ export default class RoomView extends React.Component<IProps, IState> {
                 isRoomEncrypted={this.context.isRoomEncrypted(this.state.room.roomId)}
             />;
         } else if (showRoomUpgradeBar) {
-            aux = <RoomUpgradeWarningBar room={this.state.room} recommendation={roomVersionRecommendation} />;
+            aux = <RoomUpgradeWarningBar room={this.state.room} />;
         } else if (myMembership !== "join") {
             // We do have a room object for this room, but we're not currently in it.
             // We may have a 3rd party invite to it.
@@ -2042,7 +2042,6 @@ export default class RoomView extends React.Component<IProps, IState> {
                 highlight={this.state.room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0}
                 numUnreadMessages={this.state.numUnreadMessages}
                 onScrollToBottomClick={this.jumpToLiveTimeline}
-                roomId={this.state.roomId}
             />);
         }
 
diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx
index 6394bad3de..92ff34fbcb 100644
--- a/src/components/views/settings/ChangeAvatar.tsx
+++ b/src/components/views/settings/ChangeAvatar.tsx
@@ -71,7 +71,8 @@ export default class ChangeAvatar extends React.Component<IProps, IState> {
     }
 
     // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
-    public UNSAFE_componentWillReceiveProps(newProps): void { // eslint-disable-line camelcase
+    // eslint-disable-next-line
+    public UNSAFE_componentWillReceiveProps(newProps): void {
         if (this.avatarSet) {
             // don't clobber what the user has just set
             return;

From 02ece401031092e3e08ae5e410c53a09048aaba2 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Mon, 16 Aug 2021 09:19:58 +0100
Subject: [PATCH 15/23] Fix import path on ChangeAvatar

---
 src/components/views/settings/ChangeAvatar.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx
index 92ff34fbcb..7a3e639876 100644
--- a/src/components/views/settings/ChangeAvatar.tsx
+++ b/src/components/views/settings/ChangeAvatar.tsx
@@ -16,13 +16,13 @@ limitations under the License.
 */
 
 import React from 'react';
+import { MatrixEvent, Room } from 'matrix-js-sdk/src';
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import * as sdk from '../../../index';
 import { _t } from '../../../languageHandler';
 import Spinner from '../elements/Spinner';
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 import { mediaFromMxc } from "../../../customisations/Media";
-import { MatrixEvent, Room } from '../../../../../matrix-js-sdk/src';
 
 interface IProps {
     initialAvatarUrl?: string;

From 617e7deff54f21b7e8ad62ad2bd563a50bb22225 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germain@souquet.com>
Date: Tue, 17 Aug 2021 18:05:10 +0100
Subject: [PATCH 16/23] replace sdk.getComponent with import statements

---
 src/components/views/rooms/ReadReceiptMarker.tsx     | 4 ++--
 src/components/views/rooms/RoomDetailList.tsx        | 6 ++----
 src/components/views/rooms/RoomUpgradeWarningBar.tsx | 6 ++----
 src/components/views/settings/ChangeAvatar.tsx       | 5 ++---
 src/components/views/settings/ChangeDisplayName.tsx  | 3 +--
 src/components/views/settings/DevicesPanel.tsx       | 3 +--
 src/components/views/settings/DevicesPanelEntry.tsx  | 4 +---
 src/components/views/settings/IntegrationManager.tsx | 3 +--
 src/components/views/settings/ProfileSettings.tsx    | 5 ++---
 9 files changed, 14 insertions(+), 25 deletions(-)

diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx
index 11e7563f11..28e1ec85e9 100644
--- a/src/components/views/rooms/ReadReceiptMarker.tsx
+++ b/src/components/views/rooms/ReadReceiptMarker.tsx
@@ -21,10 +21,11 @@ import { RoomMember } from 'matrix-js-sdk/src';
 import { _t } from '../../../languageHandler';
 import { formatDate } from '../../../DateUtils';
 import NodeAnimator from "../../../NodeAnimator";
-import * as sdk from "../../../index";
 import { toPx } from "../../../utils/units";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 
+import MemberAvatar from '../avatars/MemberAvatar';
+
 interface IProps {
     // the RoomMember to show the RR for
     member?: RoomMember;
@@ -169,7 +170,6 @@ export default class ReadReceiptMarker extends React.PureComponent<IProps, IStat
     }
 
     public render(): JSX.Element {
-        const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
         if (this.state.suppressDisplay) {
             return <div ref={this.avatar} />;
         }
diff --git a/src/components/views/rooms/RoomDetailList.tsx b/src/components/views/rooms/RoomDetailList.tsx
index ee7383d7c7..cace94ce08 100644
--- a/src/components/views/rooms/RoomDetailList.tsx
+++ b/src/components/views/rooms/RoomDetailList.tsx
@@ -17,11 +17,11 @@ limitations under the License.
 import React from 'react';
 import { Room } from 'matrix-js-sdk/src';
 import classNames from 'classnames';
-import * as sdk from '../../../index';
 import dis from '../../../dispatcher/dispatcher';
 import { _t } from '../../../languageHandler';
 
 import { replaceableComponent } from "../../../utils/replaceableComponent";
+import RoomDetailRow from "./RoomDetailRow";
 
 interface IProps {
     rooms?: Room[];
@@ -31,9 +31,7 @@ interface IProps {
 @replaceableComponent("views.rooms.RoomDetailList")
 export default class RoomDetailList extends React.Component<IProps> {
     public getRows(): JSX.Element[] {
-        if (!this.props.rooms) return [];
-
-        const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow');
+        if (!this.props.rooms) return []; s;
         return this.props.rooms.map((room, index) => {
             return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />;
         });
diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.tsx b/src/components/views/rooms/RoomUpgradeWarningBar.tsx
index 6706e248e0..3380bd5392 100644
--- a/src/components/views/rooms/RoomUpgradeWarningBar.tsx
+++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx
@@ -16,12 +16,13 @@ limitations under the License.
 
 import React from 'react';
 import { MatrixEvent, Room, RoomState } from 'matrix-js-sdk/src';
-import * as sdk from '../../../index';
 import Modal from '../../../Modal';
 
 import { _t } from '../../../languageHandler';
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
+import RoomUpgradeDialog from '../dialogs/RoomUpgradeDialog';
+import AccessibleButton from '../elements/AccessibleButton';
 
 interface IProps {
     room: Room;
@@ -59,13 +60,10 @@ export default class RoomUpgradeWarningBar extends React.PureComponent<IProps, I
     };
 
     private onUpgradeClick = (): void => {
-        const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog');
         Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, { room: this.props.room });
     };
 
     public render(): JSX.Element {
-        const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
-
         let doUpgradeWarnings = (
             <div>
                 <div className="mx_RoomUpgradeWarningBar_body">
diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx
index 7a3e639876..7126fe8cc3 100644
--- a/src/components/views/settings/ChangeAvatar.tsx
+++ b/src/components/views/settings/ChangeAvatar.tsx
@@ -18,11 +18,12 @@ limitations under the License.
 import React from 'react';
 import { MatrixEvent, Room } from 'matrix-js-sdk/src';
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import * as sdk from '../../../index';
 import { _t } from '../../../languageHandler';
 import Spinner from '../elements/Spinner';
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 import { mediaFromMxc } from "../../../customisations/Media";
+import RoomAvatar from '../avatars/RoomAvatar';
+import BaseAvatar from '../avatars/BaseAvatar';
 
 interface IProps {
     initialAvatarUrl?: string;
@@ -155,7 +156,6 @@ export default class ChangeAvatar extends React.Component<IProps, IState> {
         // Having just set an avatar we just display that since it will take a little
         // time to propagate through to the RoomAvatar.
         if (this.props.room && !this.avatarSet) {
-            const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
             avatarImg = <RoomAvatar
                 room={this.props.room}
                 width={this.props.width}
@@ -163,7 +163,6 @@ export default class ChangeAvatar extends React.Component<IProps, IState> {
                 resizeMethod='crop'
             />;
         } else {
-            const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
             // XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
             avatarImg = <BaseAvatar
                 width={this.props.width}
diff --git a/src/components/views/settings/ChangeDisplayName.tsx b/src/components/views/settings/ChangeDisplayName.tsx
index 09c9ecc23e..5b9703a46b 100644
--- a/src/components/views/settings/ChangeDisplayName.tsx
+++ b/src/components/views/settings/ChangeDisplayName.tsx
@@ -17,10 +17,10 @@ limitations under the License.
 */
 
 import React from 'react';
-import * as sdk from '../../../index';
 import { MatrixClientPeg } from '../../../MatrixClientPeg';
 import { _t } from '../../../languageHandler';
 import { replaceableComponent } from "../../../utils/replaceableComponent";
+import EditableTextContainer from "../elements/EditableTextContainer";
 
 @replaceableComponent("views.settings.ChangeDisplayName")
 export default class ChangeDisplayName extends React.Component {
@@ -42,7 +42,6 @@ export default class ChangeDisplayName extends React.Component {
     };
 
     public render(): JSX.Element {
-        const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer');
         return (
             <EditableTextContainer
                 getInitialValue={this.getDisplayName}
diff --git a/src/components/views/settings/DevicesPanel.tsx b/src/components/views/settings/DevicesPanel.tsx
index 6fd7f90c8e..b10820d6a5 100644
--- a/src/components/views/settings/DevicesPanel.tsx
+++ b/src/components/views/settings/DevicesPanel.tsx
@@ -20,12 +20,12 @@ import React from 'react';
 import classNames from 'classnames';
 import { IMyDevice } from "matrix-js-sdk/src/client";
 
-import * as sdk from '../../../index';
 import { MatrixClientPeg } from '../../../MatrixClientPeg';
 import { _t } from '../../../languageHandler';
 import Modal from '../../../Modal';
 import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
+import InteractiveAuthDialog from "../dialogs/InteractiveAuthDialog";
 
 interface IProps {
     className?: string;
@@ -119,7 +119,6 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
             }
 
             // pop up an interactive auth dialog
-            const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
 
             const numDevices = this.state.selectedDevices.length;
             const dialogAesthetics = {
diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx
index d44147f591..3762f7be83 100644
--- a/src/components/views/settings/DevicesPanelEntry.tsx
+++ b/src/components/views/settings/DevicesPanelEntry.tsx
@@ -18,12 +18,12 @@ limitations under the License.
 import React from 'react';
 import { IMyDevice } from 'matrix-js-sdk/src';
 
-import * as sdk from '../../../index';
 import { _t } from '../../../languageHandler';
 import { MatrixClientPeg } from '../../../MatrixClientPeg';
 import { formatDate } from '../../../DateUtils';
 import StyledCheckbox from '../elements/StyledCheckbox';
 import { replaceableComponent } from "../../../utils/replaceableComponent";
+import EditableTextContainer from "../elements/EditableTextContainer";
 
 interface IProps {
     device?: IMyDevice;
@@ -52,8 +52,6 @@ export default class DevicesPanelEntry extends React.Component<IProps> {
     };
 
     public render(): JSX.Element {
-        const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer');
-
         const device = this.props.device;
 
         let lastSeen = "";
diff --git a/src/components/views/settings/IntegrationManager.tsx b/src/components/views/settings/IntegrationManager.tsx
index f43fb55004..7b221aceec 100644
--- a/src/components/views/settings/IntegrationManager.tsx
+++ b/src/components/views/settings/IntegrationManager.tsx
@@ -17,12 +17,12 @@ limitations under the License.
 */
 
 import React from 'react';
-import * as sdk from '../../../index';
 import { _t } from '../../../languageHandler';
 import dis from '../../../dispatcher/dispatcher';
 import { Key } from "../../../Keyboard";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 import { ActionPayload } from '../../../dispatcher/payloads';
+import Spinner from "../elements/Spinner";
 
 interface IProps {
     // false to display an error saying that we couldn't connect to the integration manager
@@ -85,7 +85,6 @@ export default class IntegrationManager extends React.Component<IProps, IState>
 
     public render(): JSX.Element {
         if (this.props.loading) {
-            const Spinner = sdk.getComponent("elements.Spinner");
             return (
                 <div className='mx_IntegrationManager_loading'>
                     <h3>{ _t("Connecting to integration manager...") }</h3>
diff --git a/src/components/views/settings/ProfileSettings.tsx b/src/components/views/settings/ProfileSettings.tsx
index 888ff8967b..9bd7179f08 100644
--- a/src/components/views/settings/ProfileSettings.tsx
+++ b/src/components/views/settings/ProfileSettings.tsx
@@ -19,12 +19,13 @@ import { _t } from "../../../languageHandler";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import Field from "../elements/Field";
 import { getHostingLink } from '../../../utils/HostingLink';
-import * as sdk from "../../../index";
 import { OwnProfileStore } from "../../../stores/OwnProfileStore";
 import Modal from "../../../Modal";
 import ErrorDialog from "../dialogs/ErrorDialog";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 import { mediaFromMxc } from "../../../customisations/Media";
+import AccessibleButton from '../elements/AccessibleButton';
+import AvatarSetting from './AvatarSetting';
 
 interface IProps {
 
@@ -174,8 +175,6 @@ export default class ProfileSettings extends React.Component<IProps, IState> {
             </span>;
         }
 
-        const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
-        const AvatarSetting = sdk.getComponent('settings.AvatarSetting');
         return (
             <form
                 onSubmit={this.saveProfile}

From da127ecb765c2f52b06d6bad96beb1df390efc61 Mon Sep 17 00:00:00 2001
From: Germain <germain@souquet.com>
Date: Wed, 25 Aug 2021 08:56:21 +0100
Subject: [PATCH 17/23] Relative imports from the js-sdk
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Šimon Brandner <simon.bra.ag@gmail.com>
---
 src/components/views/rooms/ReadReceiptMarker.tsx | 2 +-
 src/components/views/settings/ChangeAvatar.tsx   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx
index 28e1ec85e9..c1bd533d39 100644
--- a/src/components/views/rooms/ReadReceiptMarker.tsx
+++ b/src/components/views/rooms/ReadReceiptMarker.tsx
@@ -16,7 +16,7 @@ limitations under the License.
 */
 
 import React, { createRef } from 'react';
-import { RoomMember } from 'matrix-js-sdk/src';
+import { RoomMember } from "matrix-js-sdk/src/models/room-member";
 
 import { _t } from '../../../languageHandler';
 import { formatDate } from '../../../DateUtils';
diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx
index 7126fe8cc3..e629b9df08 100644
--- a/src/components/views/settings/ChangeAvatar.tsx
+++ b/src/components/views/settings/ChangeAvatar.tsx
@@ -73,7 +73,7 @@ export default class ChangeAvatar extends React.Component<IProps, IState> {
 
     // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
     // eslint-disable-next-line
-    public UNSAFE_componentWillReceiveProps(newProps): void {
+    public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
         if (this.avatarSet) {
             // don't clobber what the user has just set
             return;

From 6945e3f103c017be3824b5a12e9dfe00046ac28a Mon Sep 17 00:00:00 2001
From: Germain Souquet <germains@element.io>
Date: Wed, 25 Aug 2021 09:05:07 +0100
Subject: [PATCH 18/23] Fix ProfileSettings types

---
 src/components/views/elements/AccessibleButton.tsx |  4 ++--
 src/components/views/settings/ProfileSettings.tsx  | 10 +++-------
 2 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx
index 0ce9a3a030..75b6890112 100644
--- a/src/components/views/elements/AccessibleButton.tsx
+++ b/src/components/views/elements/AccessibleButton.tsx
@@ -19,7 +19,7 @@ import React, { ReactHTML } from 'react';
 import { Key } from '../../../Keyboard';
 import classnames from 'classnames';
 
-export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Element>;
+export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Element> | React.FormEvent<Element>;
 
 /**
  * children: React's magic prop. Represents all children given to the element.
@@ -39,7 +39,7 @@ interface IProps extends React.InputHTMLAttributes<Element> {
     tabIndex?: number;
     disabled?: boolean;
     className?: string;
-    onClick(e?: ButtonEvent): void;
+    onClick(e?: ButtonEvent): void | Promise<void>;
 }
 
 interface IAccessibleButtonProps extends React.InputHTMLAttributes<Element> {
diff --git a/src/components/views/settings/ProfileSettings.tsx b/src/components/views/settings/ProfileSettings.tsx
index 9bd7179f08..e6e68299cb 100644
--- a/src/components/views/settings/ProfileSettings.tsx
+++ b/src/components/views/settings/ProfileSettings.tsx
@@ -27,10 +27,6 @@ import { mediaFromMxc } from "../../../customisations/Media";
 import AccessibleButton from '../elements/AccessibleButton';
 import AvatarSetting from './AvatarSetting';
 
-interface IProps {
-
-}
-
 interface IState {
     userId?: string;
     originalDisplayName?: string;
@@ -42,9 +38,9 @@ interface IState {
 }
 
 @replaceableComponent("views.settings.ProfileSettings")
-export default class ProfileSettings extends React.Component<IProps, IState> {
+export default class ProfileSettings extends React.Component<{}, IState> {
     private avatarUpload: React.RefObject<HTMLInputElement> = createRef();
-    constructor(props: IProps) {
+    constructor(props: {}) {
         super(props);
 
         const client = MatrixClientPeg.get();
@@ -205,7 +201,7 @@ export default class ProfileSettings extends React.Component<IProps, IState> {
                         </p>
                     </div>
                     <AvatarSetting
-                        avatarUrl={this.state.avatarUrl}
+                        avatarUrl={this.state.avatarUrl.toString()}
                         avatarName={this.state.displayName || this.state.userId}
                         avatarAltText={_t("Profile picture")}
                         uploadAvatar={this.uploadAvatar}

From 450140befd77258d3190fa0a0a6999f05739a553 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germains@element.io>
Date: Wed, 25 Aug 2021 09:34:16 +0100
Subject: [PATCH 19/23] Fix linting issues

---
 .eslintrc.js                                     |  5 +++++
 src/components/structures/RoomDirectory.tsx      |  2 +-
 src/components/views/avatars/MemberAvatar.tsx    |  1 +
 src/components/views/right_panel/UserInfo.tsx    |  2 +-
 src/components/views/rooms/ReadReceiptMarker.tsx |  8 ++++----
 src/components/views/rooms/RoomDetailList.tsx    |  2 +-
 src/components/views/rooms/RoomHeader.tsx        |  2 +-
 src/components/views/settings/DevicesPanel.tsx   | 10 ++++------
 8 files changed, 18 insertions(+), 14 deletions(-)

diff --git a/.eslintrc.js b/.eslintrc.js
index 827b373949..9d68942228 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -63,6 +63,11 @@ module.exports = {
             "@typescript-eslint/ban-ts-comment": "off",
         },
     }],
+    settings: {
+        react: {
+            version: "detect",
+        }
+    }
 };
 
 function buildRestrictedPropertiesOptions(properties, message) {
diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx
index 8d8609d1cf..3c5f99cc7d 100644
--- a/src/components/structures/RoomDirectory.tsx
+++ b/src/components/structures/RoomDirectory.tsx
@@ -347,7 +347,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
         });
     }
 
-    private onRoomClicked = (room: IPublicRoomsChunkRoom, ev: ButtonEvent) => {
+    private onRoomClicked = (room: IPublicRoomsChunkRoom, ev: React.MouseEvent) => {
         // If room was shift-clicked, remove it from the room directory
         if (ev.shiftKey && !this.state.selectedCommunityId) {
             ev.preventDefault();
diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx
index 11c24a5981..3c734705b7 100644
--- a/src/components/views/avatars/MemberAvatar.tsx
+++ b/src/components/views/avatars/MemberAvatar.tsx
@@ -36,6 +36,7 @@ interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" |
     // Whether the onClick of the avatar should be overriden to dispatch `Action.ViewUser`
     viewUserOnClick?: boolean;
     title?: string;
+    style?: any;
 }
 
 interface IState {
diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx
index 138f5bf9fe..d15f349d62 100644
--- a/src/components/views/right_panel/UserInfo.tsx
+++ b/src/components/views/right_panel/UserInfo.tsx
@@ -428,7 +428,7 @@ const UserOptionsSection: React.FC<{
     let directMessageButton;
     if (!isMe) {
         directMessageButton = (
-            <AccessibleButton onClick={() => openDMForUser(cli, member.userId)} className="mx_UserInfo_field">
+            <AccessibleButton onClick={() => { openDMForUser(cli, member.userId); }} className="mx_UserInfo_field">
                 { _t('Direct message') }
             </AccessibleButton>
         );
diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx
index c1bd533d39..cfc535b23d 100644
--- a/src/components/views/rooms/ReadReceiptMarker.tsx
+++ b/src/components/views/rooms/ReadReceiptMarker.tsx
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import React, { createRef } from 'react';
+import React, { createRef, RefObject } from 'react';
 import { RoomMember } from "matrix-js-sdk/src/models/room-member";
 
 import { _t } from '../../../languageHandler';
@@ -75,7 +75,7 @@ interface IReadReceiptMarkerStyle {
 
 @replaceableComponent("views.rooms.ReadReceiptMarker")
 export default class ReadReceiptMarker extends React.PureComponent<IProps, IState> {
-    private avatar: React.RefObject<HTMLDivElement> = createRef();
+    private avatar: React.RefObject<HTMLDivElement | HTMLImageElement | HTMLSpanElement> = createRef();
 
     static defaultProps = {
         leftOffset: 0,
@@ -171,7 +171,7 @@ export default class ReadReceiptMarker extends React.PureComponent<IProps, IStat
 
     public render(): JSX.Element {
         if (this.state.suppressDisplay) {
-            return <div ref={this.avatar} />;
+            return <div ref={this.avatar as RefObject<HTMLDivElement>} />;
         }
 
         const style = {
@@ -210,7 +210,7 @@ export default class ReadReceiptMarker extends React.PureComponent<IProps, IStat
                     style={style}
                     title={title}
                     onClick={this.props.onClick}
-                    inputRef={this.avatar}
+                    inputRef={this.avatar as RefObject<HTMLImageElement>}
                 />
             </NodeAnimator>
         );
diff --git a/src/components/views/rooms/RoomDetailList.tsx b/src/components/views/rooms/RoomDetailList.tsx
index cace94ce08..ed2a1fcb44 100644
--- a/src/components/views/rooms/RoomDetailList.tsx
+++ b/src/components/views/rooms/RoomDetailList.tsx
@@ -31,7 +31,7 @@ interface IProps {
 @replaceableComponent("views.rooms.RoomDetailList")
 export default class RoomDetailList extends React.Component<IProps> {
     public getRows(): JSX.Element[] {
-        if (!this.props.rooms) return []; s;
+        if (!this.props.rooms) return [];
         return this.props.rooms.map((room, index) => {
             return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />;
         });
diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx
index 15b25ed64b..d0e438bcda 100644
--- a/src/components/views/rooms/RoomHeader.tsx
+++ b/src/components/views/rooms/RoomHeader.tsx
@@ -195,7 +195,7 @@ export default class RoomHeader extends React.Component<IProps> {
             videoCallButton =
                 <AccessibleTooltipButton
                     className="mx_RoomHeader_button mx_RoomHeader_videoCallButton"
-                    onClick={(ev) => ev.shiftKey ?
+                    onClick={(ev: React.MouseEvent<Element>) => ev.shiftKey ?
                         this.displayInfoDialogAboutScreensharing() : this.props.onCallPlaced(PlaceCallType.Video)}
                     title={_t("Video call")} />;
         }
diff --git a/src/components/views/settings/DevicesPanel.tsx b/src/components/views/settings/DevicesPanel.tsx
index b10820d6a5..4b1fb280a7 100644
--- a/src/components/views/settings/DevicesPanel.tsx
+++ b/src/components/views/settings/DevicesPanel.tsx
@@ -26,6 +26,9 @@ import Modal from '../../../Modal';
 import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 import InteractiveAuthDialog from "../dialogs/InteractiveAuthDialog";
+import DevicesPanelEntry from "./DevicesPanelEntry";
+import Spinner from "../elements/Spinner";
+import AccessibleButton from "../elements/AccessibleButton";
 
 interface IProps {
     className?: string;
@@ -175,7 +178,6 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
     }
 
     private renderDevice = (device: IMyDevice): JSX.Element => {
-        const DevicesPanelEntry = sdk.getComponent('settings.DevicesPanelEntry');
         return <DevicesPanelEntry
             key={device.device_id}
             device={device}
@@ -185,9 +187,6 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
     };
 
     public render(): JSX.Element {
-        const Spinner = sdk.getComponent("elements.Spinner");
-        const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
-
         if (this.state.deviceLoadError !== undefined) {
             const classes = classNames(this.props.className, "error");
             return (
@@ -200,8 +199,7 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
         const devices = this.state.devices;
         if (devices === undefined) {
             // still loading
-            const classes = this.props.className;
-            return <Spinner className={classes} />;
+            return <Spinner />;
         }
 
         devices.sort(this.deviceCompare);

From 7938961d2731d88d4e56ce149ed3bd32d321ee78 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germains@element.io>
Date: Wed, 25 Aug 2021 09:48:29 +0100
Subject: [PATCH 20/23] Update js-sdk imports to target individual files

---
 src/components/views/rooms/RoomUpgradeWarningBar.tsx | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.tsx b/src/components/views/rooms/RoomUpgradeWarningBar.tsx
index 3380bd5392..4c1216b620 100644
--- a/src/components/views/rooms/RoomUpgradeWarningBar.tsx
+++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx
@@ -15,7 +15,10 @@ limitations under the License.
 */
 
 import React from 'react';
-import { MatrixEvent, Room, RoomState } from 'matrix-js-sdk/src';
+import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
+import { Room } from 'matrix-js-sdk/src/models/room';
+import { RoomState } from 'matrix-js-sdk/src/models/room-state';
+
 import Modal from '../../../Modal';
 
 import { _t } from '../../../languageHandler';

From e1e0278190faf78b9dcca8ccd5b88441dd307380 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germains@element.io>
Date: Wed, 25 Aug 2021 10:04:54 +0100
Subject: [PATCH 21/23] Update js-sdk imports to target individual files

---
 src/components/views/settings/ChangeAvatar.tsx      | 3 ++-
 src/components/views/settings/DevicesPanelEntry.tsx | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx
index e629b9df08..08ecd3d065 100644
--- a/src/components/views/settings/ChangeAvatar.tsx
+++ b/src/components/views/settings/ChangeAvatar.tsx
@@ -16,7 +16,8 @@ limitations under the License.
 */
 
 import React from 'react';
-import { MatrixEvent, Room } from 'matrix-js-sdk/src';
+import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
+import { Room } from 'matrix-js-sdk/src/models/room';
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { _t } from '../../../languageHandler';
 import Spinner from '../elements/Spinner';
diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx
index 3762f7be83..940ccf8ba1 100644
--- a/src/components/views/settings/DevicesPanelEntry.tsx
+++ b/src/components/views/settings/DevicesPanelEntry.tsx
@@ -16,7 +16,7 @@ limitations under the License.
 */
 
 import React from 'react';
-import { IMyDevice } from 'matrix-js-sdk/src';
+import { IMyDevice } from 'matrix-js-sdk/src/client';
 
 import { _t } from '../../../languageHandler';
 import { MatrixClientPeg } from '../../../MatrixClientPeg';

From cb8e62c0b2547b2c1f9745210c1efe27f67d370f Mon Sep 17 00:00:00 2001
From: Germain Souquet <germains@element.io>
Date: Thu, 26 Aug 2021 08:02:36 +0100
Subject: [PATCH 22/23] human linter and copyright fixer

---
 src/components/views/rooms/RoomDetailList.tsx        | 4 ++--
 src/components/views/rooms/RoomUpgradeWarningBar.tsx | 2 +-
 src/components/views/rooms/SimpleRoomHeader.tsx      | 3 +--
 src/components/views/rooms/TopUnreadMessagesBar.tsx  | 3 +--
 src/components/views/settings/ChangeAvatar.tsx       | 7 +++----
 src/components/views/settings/ChangeDisplayName.tsx  | 3 +--
 src/components/views/settings/DevicesPanel.tsx       | 3 +--
 src/components/views/settings/DevicesPanelEntry.tsx  | 2 +-
 src/components/views/settings/IntegrationManager.tsx | 3 +--
 src/components/views/settings/ProfileSettings.tsx    | 2 ++
 10 files changed, 14 insertions(+), 18 deletions(-)

diff --git a/src/components/views/rooms/RoomDetailList.tsx b/src/components/views/rooms/RoomDetailList.tsx
index ed2a1fcb44..869ab9e8f3 100644
--- a/src/components/views/rooms/RoomDetailList.tsx
+++ b/src/components/views/rooms/RoomDetailList.tsx
@@ -30,14 +30,14 @@ interface IProps {
 
 @replaceableComponent("views.rooms.RoomDetailList")
 export default class RoomDetailList extends React.Component<IProps> {
-    public getRows(): JSX.Element[] {
+    private getRows(): JSX.Element[] {
         if (!this.props.rooms) return [];
         return this.props.rooms.map((room, index) => {
             return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />;
         });
     }
 
-    public onDetailsClick = (ev: React.MouseEvent, room: Room): void => {
+    private onDetailsClick = (ev: React.MouseEvent, room: Room): void => {
         dis.dispatch({
             action: 'view_room',
             room_id: room.roomId,
diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.tsx b/src/components/views/rooms/RoomUpgradeWarningBar.tsx
index 4c1216b620..eb334ab825 100644
--- a/src/components/views/rooms/RoomUpgradeWarningBar.tsx
+++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2018-2021 New Vector Ltd
+Copyright 2018-2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/src/components/views/rooms/SimpleRoomHeader.tsx b/src/components/views/rooms/SimpleRoomHeader.tsx
index b81e906559..d6effaceb4 100644
--- a/src/components/views/rooms/SimpleRoomHeader.tsx
+++ b/src/components/views/rooms/SimpleRoomHeader.tsx
@@ -1,6 +1,5 @@
 /*
-Copyright 2016 OpenMarket Ltd
-Copyright 2021 New Vector Ltd
+Copyright 2016-2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/src/components/views/rooms/TopUnreadMessagesBar.tsx b/src/components/views/rooms/TopUnreadMessagesBar.tsx
index 01797299cf..ec2472f966 100644
--- a/src/components/views/rooms/TopUnreadMessagesBar.tsx
+++ b/src/components/views/rooms/TopUnreadMessagesBar.tsx
@@ -1,7 +1,6 @@
 /*
-Copyright 2016 OpenMarket Ltd
 Copyright 2017 Vector Creations Ltd
-Copyright 2019-2021 New Vector Ltd
+Copyright 2016-2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx
index 08ecd3d065..36178540f7 100644
--- a/src/components/views/settings/ChangeAvatar.tsx
+++ b/src/components/views/settings/ChangeAvatar.tsx
@@ -1,6 +1,5 @@
 /*
-Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2021 New Vector Ltd
+Copyright 2015-2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -90,7 +89,7 @@ export default class ChangeAvatar extends React.Component<IProps, IState> {
         }
     }
 
-    public onRoomStateEvents = (ev: MatrixEvent) => {
+    private onRoomStateEvents = (ev: MatrixEvent) => {
         if (!this.props.room) {
             return;
         }
@@ -106,7 +105,7 @@ export default class ChangeAvatar extends React.Component<IProps, IState> {
         }
     };
 
-    public setAvatarFromFile(file): Promise<{}> {
+    private setAvatarFromFile(file: File): Promise<{}> {
         let newUrl = null;
 
         this.setState({
diff --git a/src/components/views/settings/ChangeDisplayName.tsx b/src/components/views/settings/ChangeDisplayName.tsx
index 5b9703a46b..016f519dd9 100644
--- a/src/components/views/settings/ChangeDisplayName.tsx
+++ b/src/components/views/settings/ChangeDisplayName.tsx
@@ -1,7 +1,6 @@
 /*
-Copyright 2015, 2016 OpenMarket Ltd
 Copyright 2018 - 2021 New Vector Ltd
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2015-2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/src/components/views/settings/DevicesPanel.tsx b/src/components/views/settings/DevicesPanel.tsx
index 4b1fb280a7..b6797b8ad5 100644
--- a/src/components/views/settings/DevicesPanel.tsx
+++ b/src/components/views/settings/DevicesPanel.tsx
@@ -1,7 +1,6 @@
 /*
-Copyright 2016 OpenMarket Ltd
 Copyright 2019 The Matrix.org Foundation C.I.C.
-Copyright 2021 New Vector Ltd
+Copyright 2016-2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx
index 940ccf8ba1..b589ffc7a1 100644
--- a/src/components/views/settings/DevicesPanelEntry.tsx
+++ b/src/components/views/settings/DevicesPanelEntry.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2016 OpenMarket Ltd
+Copyright 2016-2021 The Matrix.org Foundation C.I.C.
 Copyright 2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/src/components/views/settings/IntegrationManager.tsx b/src/components/views/settings/IntegrationManager.tsx
index 7b221aceec..f9b3f67fad 100644
--- a/src/components/views/settings/IntegrationManager.tsx
+++ b/src/components/views/settings/IntegrationManager.tsx
@@ -1,6 +1,5 @@
 /*
-Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2015-2021 The Matrix.org Foundation C.I.C.
 Copyright 2021 New Vector Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/src/components/views/settings/ProfileSettings.tsx b/src/components/views/settings/ProfileSettings.tsx
index e6e68299cb..9e1f0444b3 100644
--- a/src/components/views/settings/ProfileSettings.tsx
+++ b/src/components/views/settings/ProfileSettings.tsx
@@ -1,5 +1,6 @@
 /*
 Copyright 2019-2021 New Vector Ltd
+Copyright 2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -40,6 +41,7 @@ interface IState {
 @replaceableComponent("views.settings.ProfileSettings")
 export default class ProfileSettings extends React.Component<{}, IState> {
     private avatarUpload: React.RefObject<HTMLInputElement> = createRef();
+
     constructor(props: {}) {
         super(props);
 

From ae16695713eb47f0034d7b61b16ab9c8050ed334 Mon Sep 17 00:00:00 2001
From: Germain Souquet <germains@element.io>
Date: Wed, 1 Sep 2021 10:19:25 +0100
Subject: [PATCH 23/23] Fix Apache copyright headers

---
 src/components/views/rooms/TopUnreadMessagesBar.tsx  | 3 +--
 src/components/views/settings/ChangeDisplayName.tsx  | 3 +--
 src/components/views/settings/DevicesPanel.tsx       | 3 +--
 src/components/views/settings/DevicesPanelEntry.tsx  | 3 +--
 src/components/views/settings/IntegrationManager.tsx | 3 +--
 src/components/views/settings/ProfileSettings.tsx    | 3 +--
 6 files changed, 6 insertions(+), 12 deletions(-)

diff --git a/src/components/views/rooms/TopUnreadMessagesBar.tsx b/src/components/views/rooms/TopUnreadMessagesBar.tsx
index ec2472f966..14f9a27f2d 100644
--- a/src/components/views/rooms/TopUnreadMessagesBar.tsx
+++ b/src/components/views/rooms/TopUnreadMessagesBar.tsx
@@ -1,6 +1,5 @@
 /*
-Copyright 2017 Vector Creations Ltd
-Copyright 2016-2021 The Matrix.org Foundation C.I.C.
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/src/components/views/settings/ChangeDisplayName.tsx b/src/components/views/settings/ChangeDisplayName.tsx
index 016f519dd9..9f0f813ec6 100644
--- a/src/components/views/settings/ChangeDisplayName.tsx
+++ b/src/components/views/settings/ChangeDisplayName.tsx
@@ -1,6 +1,5 @@
 /*
-Copyright 2018 - 2021 New Vector Ltd
-Copyright 2015-2021 The Matrix.org Foundation C.I.C.
+Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/src/components/views/settings/DevicesPanel.tsx b/src/components/views/settings/DevicesPanel.tsx
index b6797b8ad5..5e297bbea6 100644
--- a/src/components/views/settings/DevicesPanel.tsx
+++ b/src/components/views/settings/DevicesPanel.tsx
@@ -1,6 +1,5 @@
 /*
-Copyright 2019 The Matrix.org Foundation C.I.C.
-Copyright 2016-2021 New Vector Ltd
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx
index b589ffc7a1..d033bc41a9 100644
--- a/src/components/views/settings/DevicesPanelEntry.tsx
+++ b/src/components/views/settings/DevicesPanelEntry.tsx
@@ -1,6 +1,5 @@
 /*
-Copyright 2016-2021 The Matrix.org Foundation C.I.C.
-Copyright 2021 New Vector Ltd
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/src/components/views/settings/IntegrationManager.tsx b/src/components/views/settings/IntegrationManager.tsx
index f9b3f67fad..0b880c019f 100644
--- a/src/components/views/settings/IntegrationManager.tsx
+++ b/src/components/views/settings/IntegrationManager.tsx
@@ -1,6 +1,5 @@
 /*
-Copyright 2015-2021 The Matrix.org Foundation C.I.C.
-Copyright 2021 New Vector Ltd
+Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/src/components/views/settings/ProfileSettings.tsx b/src/components/views/settings/ProfileSettings.tsx
index 9e1f0444b3..9563280550 100644
--- a/src/components/views/settings/ProfileSettings.tsx
+++ b/src/components/views/settings/ProfileSettings.tsx
@@ -1,6 +1,5 @@
 /*
-Copyright 2019-2021 New Vector Ltd
-Copyright 2021 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.