From 3ff628254d41908b5224f5d1caec92caf3b9191c Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Tue, 25 Jul 2017 15:56:40 +0100
Subject: [PATCH 01/10] CSS for group userlist

---
 src/components/structures/RightPanel.js | 98 ++++++++++++++-----------
 1 file changed, 54 insertions(+), 44 deletions(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 6c5c1fe1..f3c16021 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -1,5 +1,6 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,8 +15,6 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-'use strict';
-
 import React from 'react';
 import { _t } from 'matrix-react-sdk/lib/languageHandler';
 import sdk from 'matrix-react-sdk';
@@ -33,19 +32,21 @@ module.exports = React.createClass({
     propTypes: {
         userId: React.PropTypes.string, // if showing an orphaned MemberInfo page, this is set
         roomId: React.PropTypes.string, // if showing panels for a given room, this is set
+        groupId: React.PropTypes.string, // if showing panels for a given group, this is set
         collapsed: React.PropTypes.bool, // currently unused property to request for a minimized view of the panel
     },
 
     Phase: {
-        MemberList: 'MemberList',
+        RoomMemberList: 'RoomMemberList',
+        GroupMemberList: 'GroupMemberList',
         FilePanel: 'FilePanel',
         NotificationPanel: 'NotificationPanel',
-        MemberInfo: 'MemberInfo',
+        RoomMemberInfo: 'RoomMemberInfo',
     },
 
     componentWillMount: function() {
         this.dispatcherRef = dis.register(this.onAction);
-        var cli = MatrixClientPeg.get();
+        const cli = MatrixClientPeg.get();
         cli.on("RoomState.members", this.onRoomStateMember);
     },
 
@@ -58,21 +59,25 @@ module.exports = React.createClass({
 
     getInitialState: function() {
         if (this.props.userId) {
-            var member = new Matrix.RoomMember(null, this.props.userId);
+            const member = new Matrix.RoomMember(null, this.props.userId);
             return {
-                phase: this.Phase.MemberInfo,
+                phase: this.Phase.RoomMemberInfo,
                 member: member,
             };
+        } else if (this.props.groupId) {
+            return {
+                phase: this.Phase.GroupMemberList
+            };
         } else {
             return {
-                phase: this.Phase.MemberList
+                phase: this.Phase.RoomMemberList
             };
         }
     },
 
     onMemberListButtonClick: function() {
         Analytics.trackEvent('Right Panel', 'Member List Button', 'click');
-        this.setState({ phase: this.Phase.MemberList });
+        this.setState({ phase: this.Phase.RoomMemberList });
     },
 
     onFileListButtonClick: function() {
@@ -106,10 +111,10 @@ module.exports = React.createClass({
 
     onRoomStateMember: function(ev, state, member) {
         // redraw the badge on the membership list
-        if (this.state.phase == this.Phase.MemberList && member.roomId === this.props.roomId) {
+        if (this.state.phase == this.Phase.RoomMemberList && member.roomId === this.props.roomId) {
             this._delayedUpdate();
         }
-        else if (this.state.phase === this.Phase.MemberInfo && member.roomId === this.props.roomId &&
+        else if (this.state.phase === this.Phase.RoomMemberInfo && member.roomId === this.props.roomId &&
                 member.userId === this.state.member.userId) {
             // refresh the member info (e.g. new power level)
             this._delayedUpdate();
@@ -127,39 +132,45 @@ module.exports = React.createClass({
             });
             if (payload.member) {
                 this.setState({
-                    phase: this.Phase.MemberInfo,
+                    phase: this.Phase.RoomMemberInfo,
                     member: payload.member,
                 });
-            }
-            else {
-                this.setState({
-                    phase: this.Phase.MemberList
-                });
+            } else {
+                if (this.props.roomId) {
+                    this.setState({
+                        phase: this.Phase.RoomMemberList
+                    });
+                } else if (this.props.groupId) {
+                    this.setState({
+                        phase: this.Phase.GroupMemberList
+                    });
+                }
             }
         }
         else if (payload.action === "view_room") {
-            if (this.state.phase === this.Phase.MemberInfo) {
+            if (this.state.phase === this.Phase.RoomMemberInfo) {
                 this.setState({
-                    phase: this.Phase.MemberList
+                    phase: this.Phase.RoomMemberList
                 });
             }
         }
     },
 
     render: function() {
-        var MemberList = sdk.getComponent('rooms.MemberList');
-        var NotificationPanel = sdk.getComponent('structures.NotificationPanel');
-        var FilePanel = sdk.getComponent('structures.FilePanel');
-        var TintableSvg = sdk.getComponent("elements.TintableSvg");
-        var buttonGroup;
-        var inviteGroup;
-        var panel;
+        const MemberList = sdk.getComponent('rooms.MemberList');
+        const GroupMemberList = sdk.getComponent('groups.GroupMemberList');
+        const NotificationPanel = sdk.getComponent('structures.NotificationPanel');
+        const FilePanel = sdk.getComponent('structures.FilePanel');
+        const TintableSvg = sdk.getComponent("elements.TintableSvg");
+        let buttonGroup;
+        let inviteGroup;
+        let panel;
 
-        var filesHighlight;
-        var membersHighlight;
-        var notificationsHighlight;
+        let filesHighlight;
+        let membersHighlight;
+        let notificationsHighlight;
         if (!this.props.collapsed) {
-            if (this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) {
+            if (this.state.phase == this.Phase.RoomMemberList || this.state.phase === this.Phase.RoomMemberInfo) {
                 membersHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>;
             }
             else if (this.state.phase == this.Phase.FilePanel) {
@@ -170,11 +181,11 @@ module.exports = React.createClass({
             }
         }
 
-        var membersBadge;
-        if ((this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) && this.props.roomId) {
-            var cli = MatrixClientPeg.get();
-            var room = cli.getRoom(this.props.roomId);
-            var user_is_in_room;
+        let membersBadge;
+        if ((this.state.phase == this.Phase.RoomMemberList || this.state.phase === this.Phase.RoomMemberInfo) && this.props.roomId) {
+            const cli = MatrixClientPeg.get();
+            const room = cli.getRoom(this.props.roomId);
+            let user_is_in_room;
             if (room) {
                 membersBadge = room.getJoinedMembers().length;
                 user_is_in_room = room.hasMembershipState(
@@ -224,17 +235,16 @@ module.exports = React.createClass({
         }
 
         if (!this.props.collapsed) {
-            if(this.props.roomId && this.state.phase == this.Phase.MemberList) {
+            if (this.props.roomId && this.state.phase == this.Phase.RoomMemberList) {
                 panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
-            }
-            else if(this.state.phase == this.Phase.MemberInfo) {
-                var MemberInfo = sdk.getComponent('rooms.MemberInfo');
+            } else if (this.props.groupId && this.state.phase == this.Phase.GroupMemberList) {
+                panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />
+            } else if(this.state.phase == this.Phase.RoomMemberInfo) {
+                const MemberInfo = sdk.getComponent('rooms.MemberInfo');
                 panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.props.userId} />
-            }
-            else if (this.state.phase == this.Phase.NotificationPanel) {
+            } else if (this.state.phase == this.Phase.NotificationPanel) {
                 panel = <NotificationPanel />
-            }
-            else if (this.state.phase == this.Phase.FilePanel) {
+            } else if (this.state.phase == this.Phase.FilePanel) {
                 panel = <FilePanel roomId={this.props.roomId} />
             }
         }
@@ -243,7 +253,7 @@ module.exports = React.createClass({
             panel = <div className="mx_RightPanel_blank"></div>;
         }
 
-        var classes = "mx_RightPanel mx_fadable";
+        let classes = "mx_RightPanel mx_fadable";
         if (this.props.collapsed) {
             classes += " collapsed";
         }

From 7c4b6739fcba85a5b63d76506d5eb7cd3aebe818 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Wed, 9 Aug 2017 15:36:51 +0100
Subject: [PATCH 02/10] Add group member right panel

---
 src/components/structures/RightPanel.js | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index f3c16021..4af7c6a4 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -42,6 +42,7 @@ module.exports = React.createClass({
         FilePanel: 'FilePanel',
         NotificationPanel: 'NotificationPanel',
         RoomMemberInfo: 'RoomMemberInfo',
+        GroupMemberInfo: 'GroupMemberInfo',
     },
 
     componentWillMount: function() {
@@ -57,6 +58,10 @@ module.exports = React.createClass({
         }
     },
 
+    componentWillReceiveProps: function(newprops) {
+        this.setState(this.getInitialState());
+    },
+
     getInitialState: function() {
         if (this.props.userId) {
             const member = new Matrix.RoomMember(null, this.props.userId);
@@ -142,12 +147,19 @@ module.exports = React.createClass({
                     });
                 } else if (this.props.groupId) {
                     this.setState({
-                        phase: this.Phase.GroupMemberList
+                        phase: this.Phase.GroupMemberList,
+                        groupId: payload.groupId,
+                        member: payload.member,
                     });
                 }
             }
-        }
-        else if (payload.action === "view_room") {
+        } else if (payload.action === "view_group_user") {
+            this.setState({
+                phase: this.Phase.GroupMemberInfo,
+                groupId: payload.groupId,
+                member: payload.member,
+            });
+        } else if (payload.action === "view_room") {
             if (this.state.phase === this.Phase.RoomMemberInfo) {
                 this.setState({
                     phase: this.Phase.RoomMemberList
@@ -239,9 +251,12 @@ module.exports = React.createClass({
                 panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
             } else if (this.props.groupId && this.state.phase == this.Phase.GroupMemberList) {
                 panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />
-            } else if(this.state.phase == this.Phase.RoomMemberInfo) {
+            } else if (this.state.phase == this.Phase.RoomMemberInfo) {
                 const MemberInfo = sdk.getComponent('rooms.MemberInfo');
                 panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.props.userId} />
+            } else if (this.state.phase == this.Phase.GroupMemberInfo) {
+                const GroupMemberInfo = sdk.getComponent('groups.GroupMemberInfo');
+                panel = <GroupMemberInfo member={this.state.member} groupId={this.props.groupId} key={this.state.member.user_id} />
             } else if (this.state.phase == this.Phase.NotificationPanel) {
                 panel = <NotificationPanel />
             } else if (this.state.phase == this.Phase.FilePanel) {

From 57eb2feeb638589f44fa65e973a1e8546ff3f378 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Tue, 15 Aug 2017 13:18:16 +0100
Subject: [PATCH 03/10] Make right panel collpasing work in GroupView

---
 src/components/structures/RightPanel.js       | 101 +++++++++++-------
 src/i18n/strings/en_EN.json                   |   3 +-
 .../structures/_GroupView.scss                |   4 +
 3 files changed, 70 insertions(+), 38 deletions(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 18adcda6..acd92e3f 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -98,11 +98,15 @@ module.exports = React.createClass({
             return;
         }
 
-        // call ChatInviteDialog
-        dis.dispatch({
-            action: 'view_invite',
-            roomId: this.props.roomId,
-        });
+        if (this.state.phase === this.Phase.GroupMemberList) {
+            // TODO: display UserPickeDialog
+        } else {
+            // call UserPickerDialog
+            dis.dispatch({
+                action: 'view_invite',
+                roomId: this.props.roomId,
+            });
+        }
     },
 
     onRoomStateMember: function(ev, state, member) {
@@ -169,7 +173,6 @@ module.exports = React.createClass({
         const NotificationPanel = sdk.getComponent('structures.NotificationPanel');
         const FilePanel = sdk.getComponent('structures.FilePanel');
         const TintableSvg = sdk.getComponent("elements.TintableSvg");
-        let buttonGroup;
         let inviteGroup;
         let panel;
 
@@ -212,50 +215,72 @@ module.exports = React.createClass({
 
         }
 
+        let headerButtons = [];
         if (this.props.roomId) {
-            buttonGroup =
-                    <div className="mx_RightPanel_headerButtonGroup">
-                        <AccessibleButton className="mx_RightPanel_headerButton"
-                                title={ _t('Members') } onClick={ this.onMemberListButtonClick }>
-                            <div className="mx_RightPanel_headerButton_badge">{ membersBadge ? membersBadge : <span>&nbsp;</span>}</div>
-                            <TintableSvg src="img/icons-people.svg" width="25" height="25"/>
-                            { membersHighlight }
-                        </AccessibleButton>
-                        <AccessibleButton
-                                className="mx_RightPanel_headerButton mx_RightPanel_filebutton"
-                                title={ _t('Files') } onClick={ this.onFileListButtonClick }>
-                            <div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
-                            <TintableSvg src="img/icons-files.svg" width="25" height="25"/>
-                            { filesHighlight }
-                        </AccessibleButton>
-                        <AccessibleButton
-                                className="mx_RightPanel_headerButton mx_RightPanel_notificationbutton"
-                                title={ _t('Notifications') } onClick={ this.onNotificationListButtonClick }>
-                            <div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
-                            <TintableSvg src="img/icons-notifications.svg" width="25" height="25"/>
-                            { notificationsHighlight }
-                        </AccessibleButton>
-                        <div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" title={ _t("Hide panel") } onClick={ this.onCollapseClick }>
-                            <TintableSvg src="img/minimise.svg" width="10" height="16"/>
-                        </div>
-                    </div>;
+            headerButtons.push(
+                <AccessibleButton className="mx_RightPanel_headerButton" key="_membersButton"
+                        title={ _t('Members') } onClick={ this.onMemberListButtonClick }>
+                    <div className="mx_RightPanel_headerButton_badge">{ membersBadge ? membersBadge : <span>&nbsp;</span>}</div>
+                    <TintableSvg src="img/icons-people.svg" width="25" height="25"/>
+                    { membersHighlight }
+                </AccessibleButton>
+            );
+            headerButtons.push(
+                <AccessibleButton
+                        className="mx_RightPanel_headerButton mx_RightPanel_filebutton" key="_filesButton"
+                        title={ _t('Files') } onClick={ this.onFileListButtonClick }>
+                    <div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
+                    <TintableSvg src="img/icons-files.svg" width="25" height="25"/>
+                    { filesHighlight }
+                </AccessibleButton>
+            );
+            headerButtons.push(
+                <AccessibleButton
+                        className="mx_RightPanel_headerButton mx_RightPanel_notificationbutton" key="_notifsButton"
+                        title={ _t('Notifications') } onClick={ this.onNotificationListButtonClick }>
+                    <div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
+                    <TintableSvg src="img/icons-notifications.svg" width="25" height="25"/>
+                    { notificationsHighlight }
+                </AccessibleButton>
+            );
+        }
+
+        if (this.props.roomId || this.props.groupId) {
+            // Hiding the right panel hides it completely and relies on an 'expand' button
+            // being put in the RoomHeader or GroupView header, so only show the minimise
+            // button on these 2 screens or you won't be able to re-expand the panel.
+            headerButtons.push(
+                <div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" key="_minimizeButton"
+                    title={ _t("Hide panel") } onClick={ this.onCollapseClick }
+                >
+                    <TintableSvg src="img/minimise.svg" width="10" height="16"/>
+                </div>
+            );
         }
 
         if (!this.props.collapsed) {
             if (this.props.roomId && this.state.phase == this.Phase.RoomMemberList) {
                 panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
             } else if (this.props.groupId && this.state.phase == this.Phase.GroupMemberList) {
-                panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />
+                panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />;
+                inviteGroup = (
+                    <AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
+                        <div className="mx_RightPanel_icon" >
+                            <TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />
+                        </div>
+                        <div className="mx_RightPanel_message">{ _t('Invite to this group') }</div>
+                    </AccessibleButton>
+                );
             } else if (this.state.phase == this.Phase.RoomMemberInfo) {
                 const MemberInfo = sdk.getComponent('rooms.MemberInfo');
                 panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.state.member.userId} />
             } else if (this.state.phase == this.Phase.GroupMemberInfo) {
                 const GroupMemberInfo = sdk.getComponent('groups.GroupMemberInfo');
-                panel = <GroupMemberInfo member={this.state.member} groupId={this.props.groupId} key={this.state.member.user_id} />
+                panel = <GroupMemberInfo member={this.state.member} groupId={this.props.groupId} key={this.state.member.user_id} />;
             } else if (this.state.phase == this.Phase.NotificationPanel) {
-                panel = <NotificationPanel />
+                panel = <NotificationPanel />;
             } else if (this.state.phase == this.Phase.FilePanel) {
-                panel = <FilePanel roomId={this.props.roomId} />
+                panel = <FilePanel roomId={this.props.roomId} />;
             }
         }
 
@@ -271,7 +296,9 @@ module.exports = React.createClass({
         return (
             <aside className={classes} style={{ opacity: this.props.opacity }}>
                 <div className="mx_RightPanel_header">
-                    { buttonGroup }
+                    <div className="mx_RightPanel_headerButtonGroup">
+                        {headerButtons}
+                    </div>
                 </div>
                 { panel }
                 <div className="mx_RightPanel_footer">
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index cecb248c..81f10e16 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -205,5 +205,6 @@
   "Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.",
   "To return to your account in future you need to <u>set a password</u>": "To return to your account in future you need to <u>set a password</u>",
   "Set Password": "Set Password",
-  "Couldn't load home page": "Couldn't load home page"
+  "Couldn't load home page": "Couldn't load home page",
+  "Invite to this group": "Invite to this group"
 }
diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
index ae9f97b2..7045188d 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
@@ -70,6 +70,10 @@ limitations under the License.
     flex: 1;
 }
 
+.mx_GroupView_header_rightCol {
+    display: flex;
+}
+
 .mx_GroupView_saveButton, .mx_GroupView_cancelButton {
     display: table-cell;
 }

From 1a7a670c96296009a1d42db9c7a6cd5697141e91 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Wed, 16 Aug 2017 14:59:00 +0100
Subject: [PATCH 04/10] Show the group invite dialog

What to TODO said!
---
 src/components/structures/RightPanel.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index acd92e3f..242997bf 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -25,6 +25,7 @@ import Analytics from 'matrix-react-sdk/lib/Analytics';
 import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc';
 import Modal from 'matrix-react-sdk/lib/Modal';
 import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
+import { showGroupInviteDialog } from 'matrix-react-sdk/lib/GroupInvite';
 
 module.exports = React.createClass({
     displayName: 'RightPanel',
@@ -99,7 +100,7 @@ module.exports = React.createClass({
         }
 
         if (this.state.phase === this.Phase.GroupMemberList) {
-            // TODO: display UserPickeDialog
+            showGroupInviteDialog(this.props.groupId);
         } else {
             // call UserPickerDialog
             dis.dispatch({

From ee6e36cd904adb49c468f3897c9b607b5f031567 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 18 Aug 2017 11:23:55 +0100
Subject: [PATCH 05/10] Allow extra tiles to be put into room sub lists

So we can put in tiles for group invites & other stuff
---
 src/components/structures/RoomSubList.js | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js
index 7c6b1622..0ca595c8 100644
--- a/src/components/structures/RoomSubList.js
+++ b/src/components/structures/RoomSubList.js
@@ -88,6 +88,7 @@ var RoomSubList = React.createClass({
         searchFilter: React.PropTypes.string,
         emptyContent: React.PropTypes.node, // content shown if the list is empty
         headerItems: React.PropTypes.node, // content shown in the sublist header
+        extraTiles: React.PropTypes.arrayOf(React.PropTypes.node), // extra elements added beneath tiles
     },
 
     getInitialState: function() {
@@ -101,7 +102,8 @@ var RoomSubList = React.createClass({
     getDefaultProps: function() {
         return {
             onHeaderClick: function() {}, // NOP
-            onShowMoreRooms: function() {} // NOP
+            onShowMoreRooms: function() {}, // NOP
+            extraTiles: [],
         };
     },
 
@@ -532,13 +534,14 @@ var RoomSubList = React.createClass({
         var label = this.props.collapsed ? null : this.props.label;
 
         let content;
-        if (this.state.sortedList.length == 0 && !this.props.searchFilter) {
+        if (this.state.sortedList.length == 0 && !this.props.searchFilter && !this.props.extraTiles) {
             content = this.props.emptyContent;
         } else {
             content = this.makeRoomTiles();
+            content.push(...this.props.extraTiles);
         }
 
-        if (this.state.sortedList.length > 0 || this.props.editable) {
+        if (this.state.sortedList.length > 0 || this.props.extraTiles.length > 0 || this.props.editable) {
             var subList;
             var classes = "mx_RoomSubList";
 

From 07b6e215a13dbdb12ce63e139dafc2079757e693 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 18 Aug 2017 11:24:52 +0100
Subject: [PATCH 06/10] CSS for group invite tiles

---
 src/skins/vector/css/_components.scss         |  1 +
 .../views/groups/_GroupInviteTile.scss        | 74 +++++++++++++++++++
 src/skins/vector/css/themes/_base.scss        |  2 +
 3 files changed, 77 insertions(+)
 create mode 100644 src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss

diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss
index 00c19b13..3493336a 100644
--- a/src/skins/vector/css/_components.scss
+++ b/src/skins/vector/css/_components.scss
@@ -32,6 +32,7 @@
 @import "./matrix-react-sdk/views/elements/_ProgressBar.scss";
 @import "./matrix-react-sdk/views/elements/_RichText.scss";
 @import "./matrix-react-sdk/views/elements/_RoleButton.scss";
+@import "./matrix-react-sdk/views/groups/_GroupInviteTile.scss";
 @import "./matrix-react-sdk/views/login/_InteractiveAuthEntryComponents.scss";
 @import "./matrix-react-sdk/views/login/_ServerConfig.scss";
 @import "./matrix-react-sdk/views/messages/_MEmoteBody.scss";
diff --git a/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss b/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss
new file mode 100644
index 00000000..6b4034b9
--- /dev/null
+++ b/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss
@@ -0,0 +1,74 @@
+/*
+Copyright 2017 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.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_GroupInviteTile {
+    position: relative;
+    cursor: pointer;
+    font-size: 13px;
+    display: block;
+    height: 34px;
+}
+
+.mx_GroupInviteTile_nameContainer {
+    display: inline-block;
+    width: 180px;
+    height: 24px;
+}
+
+.mx_GroupInviteTile_avatarContainer {
+    display: inline-block;
+    padding-top: 5px;
+    padding-bottom: 5px;
+    padding-left: 16px;
+    padding-right: 6px;
+    width: 24px;
+    height: 24px;
+    vertical-align: middle;
+}
+
+.mx_GroupInviteTile_name {
+    display: inline-block;
+    position: relative;
+    width: 165px;
+    vertical-align: middle;
+    padding-left: 6px;
+    padding-right: 6px;
+    padding-top: 2px;
+    padding-bottom: 3px;
+    color: $roomtile-name-color;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.mx_GroupInviteTile_badge {
+    display: inline-block;
+    min-width: 15px;
+    height: 15px;
+    position: absolute;
+    right: 8px; /*gutter */
+    top: 9px;
+    border-radius: 8px;
+    color: $accent-fg-color;
+    background-color: $group-alert-color;
+    font-weight: 600;
+    font-size: 10px;
+    text-align: center;
+    padding-top: 1px;
+    padding-left: 4px;
+    padding-right: 4px;
+}
+
diff --git a/src/skins/vector/css/themes/_base.scss b/src/skins/vector/css/themes/_base.scss
index 6f613e38..a13c517b 100644
--- a/src/skins/vector/css/themes/_base.scss
+++ b/src/skins/vector/css/themes/_base.scss
@@ -22,6 +22,8 @@ $warning-color: #ff0064;
 $mention-user-pill-bg-color: #ff0064;
 $other-user-pill-bg-color: rgba(0, 0, 0, 0.1);
 
+$group-alert-color: #774f7e;
+
 $preview-bar-bg-color: #f7f7f7;
 
 // left-panel style muted accent color

From 013f2ce66aeb1e218b55635aea65736e831e752a Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Mon, 21 Aug 2017 19:34:31 +0100
Subject: [PATCH 07/10] More CSS for group membership lifecycle

---
 .../structures/_GroupView.scss                 | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
index 7045188d..f9932f6f 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
@@ -74,8 +74,12 @@ limitations under the License.
     display: flex;
 }
 
-.mx_GroupView_saveButton, .mx_GroupView_cancelButton {
-    display: table-cell;
+.mx_GroupView_membership_buttonContainer {
+    margin-top: 10px;
+}
+
+.mx_GroupView_textButton {
+    display: inline-block;
 }
 
 .mx_GroupView_header_groupid {
@@ -130,6 +134,16 @@ limitations under the License.
     top: 5px;
 }
 
+.mx_GroupView_invitedSection {
+    width: 70%;
+    padding: 20px;
+    border: 1px solid $group-alert-color;
+    margin-left: auto;
+    margin-right: auto;
+    margin-bottom: 20px;
+    text-align: center;
+}
+
 .mx_GroupView_featuredThings {
     margin-top: 20px;
 }

From a673cbcf1e4f62676926c33eb875fb2810cca70f Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 25 Aug 2017 13:00:43 +0100
Subject: [PATCH 09/10] s/member/groupMember/

---
 src/components/structures/RightPanel.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 242997bf..4d3337d0 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -277,7 +277,7 @@ module.exports = React.createClass({
                 panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.state.member.userId} />
             } else if (this.state.phase == this.Phase.GroupMemberInfo) {
                 const GroupMemberInfo = sdk.getComponent('groups.GroupMemberInfo');
-                panel = <GroupMemberInfo member={this.state.member} groupId={this.props.groupId} key={this.state.member.user_id} />;
+                panel = <GroupMemberInfo groupMember={this.state.member} groupId={this.props.groupId} key={this.state.member.user_id} />;
             } else if (this.state.phase == this.Phase.NotificationPanel) {
                 panel = <NotificationPanel />;
             } else if (this.state.phase == this.Phase.FilePanel) {

From e5912b996dc0aa882a2bb4830d7f0167724d5ffc Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Wed, 30 Aug 2017 09:23:20 +0100
Subject: [PATCH 10/10] copyright

---
 src/components/structures/RightPanel.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 4d3337d0..62adaa74 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -1,6 +1,7 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
 Copyright 2017 Vector Creations Ltd
+Copyright 2017 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.