diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index bfbc92ca05..4f33617344 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -62,6 +62,10 @@ limitations under the License. mask-image: url('$(res)/img/icons-share.svg'); } +.mx_GroupView_hostingSignup img { + margin-left: 5px; +} + .mx_GroupView_editable { border-bottom: 1px solid $strong-input-border-color ! important; min-width: 150px; diff --git a/res/css/views/context_menus/_TopLeftMenu.scss b/res/css/views/context_menus/_TopLeftMenu.scss index c15d12eb6a..113da004b8 100644 --- a/res/css/views/context_menus/_TopLeftMenu.scss +++ b/res/css/views/context_menus/_TopLeftMenu.scss @@ -15,17 +15,39 @@ limitations under the License. */ .mx_TopLeftMenu { - min-width: 180px; + min-width: 210px; border-radius: 4px; + .mx_TopLeftMenu_greyedText { + font-size: 12px; + opacity: 0.5; + } + + .mx_TopLeftMenu_upgradeLink { + font-size: 12px; + + img { + margin-left: 5px; + } + } + .mx_TopLeftMenu_section:not(:last-child) { border-bottom: 1px solid $menu-border-color; } - .mx_TopLeftMenu_section { - list-style: none; + .mx_TopLeftMenu_section_noIcon { + margin: 5px 0; + padding: 5px 20px 5px 15px; + + div:not(:first-child) { + margin-top: 5px; + } + } + + .mx_TopLeftMenu_section_withIcon { margin: 5px 0; padding: 0; + list-style: none; li.mx_TopLeftMenu_icon_home::after { mask-image: url('$(res)/img/feather-customised/home.svg'); diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index 5d85f80bfe..b2e449ac34 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -35,6 +35,14 @@ limitations under the License. margin-top: 0; } +.mx_ProfileSettings_hostingSignup { + margin-left: 20px; + + img { + margin-left: 5px; + } +} + .mx_ProfileSettings_avatar { width: 88px; height: 88px; diff --git a/res/img/external-link.svg b/res/img/external-link.svg new file mode 100644 index 0000000000..459e790fe3 --- /dev/null +++ b/res/img/external-link.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index b80f49d051..6f1aeaf624 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -21,6 +21,7 @@ import Promise from 'bluebird'; import MatrixClientPeg from '../../MatrixClientPeg'; import sdk from '../../index'; import dis from '../../dispatcher'; +import { getHostingLink } from '../../utils/HostingLink'; import { sanitizedHtmlNode } from '../../HtmlUtils'; import { _t, _td } from '../../languageHandler'; import AccessibleButton from '../views/elements/AccessibleButton'; @@ -816,6 +817,23 @@ export default React.createClass({ }); const header = this.state.editing ?

{ _t('Community Settings') }

:
; + + const hostingSignupLink = getHostingLink('community-settings'); + let hostingSignup = null; + if (hostingSignupLink) { + hostingSignup =
+ {_t( + "Want more than a community? Get your own server", {}, + { + a: sub => {sub}, + }, + )} + + + +
; + } + const changeDelayWarning = this.state.editing && this.state.isUserPrivileged ?
{ _t( @@ -830,6 +848,7 @@ export default React.createClass({
:
; return
{ header } + { hostingSignup } { changeDelayWarning } { this._getJoinableNode() } { this._getLongDescriptionNode() } diff --git a/src/components/structures/TopLeftMenuButton.js b/src/components/structures/TopLeftMenuButton.js index 4e3f609c07..b68d3a95a0 100644 --- a/src/components/structures/TopLeftMenuButton.js +++ b/src/components/structures/TopLeftMenuButton.js @@ -68,17 +68,18 @@ export default class TopLeftMenuButton extends React.Component { } } - render() { - const fallbackUserId = MatrixClientPeg.get().getUserId(); - const profileInfo = this.state.profileInfo; - let name; + _getDisplayName() { if (MatrixClientPeg.get().isGuest()) { - name = _t("Guest"); - } else if (profileInfo) { - name = profileInfo.name; + return _t("Guest"); + } else if (this.state.profileInfo) { + return this.state.profileInfo.name; } else { - name = fallbackUserId; + return MatrixClientPeg.get().getUserId(); } + } + + render() { + const name = this._getDisplayName(); let nameElement; if (!this.props.collapsed) { nameElement =
@@ -89,9 +90,9 @@ export default class TopLeftMenuButton extends React.Component { return ( { this.setState({ menuDisplayed: false }); }, diff --git a/src/components/views/context_menus/TopLeftMenu.js b/src/components/views/context_menus/TopLeftMenu.js index e3d8ef8d91..278c879404 100644 --- a/src/components/views/context_menus/TopLeftMenu.js +++ b/src/components/views/context_menus/TopLeftMenu.js @@ -15,14 +15,22 @@ limitations under the License. */ import React from 'react'; +import PropTypes from 'prop-types'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; import LogoutDialog from "../dialogs/LogoutDialog"; import Modal from "../../../Modal"; import SdkConfig from '../../../SdkConfig'; +import { getHostingLink } from '../../../utils/HostingLink'; import MatrixClientPeg from '../../../MatrixClientPeg'; export class TopLeftMenu extends React.Component { + static propTypes = { + displayName: PropTypes.string.isRequired, + userId: PropTypes.string.isRequired, + onFinished: PropTypes.func, + }; + constructor() { super(); this.viewHomePage = this.viewHomePage.bind(this); @@ -46,27 +54,48 @@ export class TopLeftMenu extends React.Component { render() { const isGuest = MatrixClientPeg.get().isGuest(); + const hostingSignupLink = getHostingLink('user-context-menu'); + let hostingSignup = null; + if (hostingSignupLink) { + hostingSignup =
+ {_t( + "Upgrade to your own domain", {}, + { + a: sub => {sub}, + }, + )} + + + +
; + } + let homePageSection = null; if (this.hasHomePage()) { - homePageSection =
    + homePageSection =
    • {_t("Home")}
    ; } let signInOutSection; if (isGuest) { - signInOutSection =
      + signInOutSection =
      • {_t("Sign in")}
      ; } else { - signInOutSection =
        + signInOutSection =
        • {_t("Sign out")}
        ; } return
        +
        +
        {this.props.displayName}
        +
        {this.props.userId}
        + {hostingSignup} +
        {homePageSection} -
          +
          • {_t("Settings")}
          {signInOutSection} diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index 649128e4f3..7765978931 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -20,6 +20,8 @@ import MatrixClientPeg from "../../../MatrixClientPeg"; import Field from "../elements/Field"; import AccessibleButton from "../elements/AccessibleButton"; import classNames from 'classnames'; +import {User} from "matrix-js-sdk"; +import { getHostingLink } from '../../../utils/HostingLink'; export default class ProfileSettings extends React.Component { constructor() { @@ -129,13 +131,32 @@ export default class ProfileSettings extends React.Component {
        ); + const hostingSignupLink = getHostingLink('user-settings'); + let hostingSignup = null; + if (hostingSignupLink) { + hostingSignup = + {_t( + "Upgrade to your own domain", {}, + { + a: sub => {sub}, + }, + )} + + + + ; + } + return (
        -

        {this.state.userId}

        +

        + {this.state.userId} + {hostingSignup} +

        diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 696bd8152c..99ffd71c84 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -500,6 +500,7 @@ "Phone Number": "Phone Number", "Profile picture": "Profile picture", "Upload profile picture": "Upload profile picture", + "Upgrade to your own domain": "Upgrade to your own domain", "Display Name": "Display Name", "Save": "Save", "Flair": "Flair", @@ -1314,6 +1315,7 @@ "Leave %(groupName)s?": "Leave %(groupName)s?", "Unable to leave community": "Unable to leave community", "Community Settings": "Community Settings", + "Want more than a community? Get your own server": "Want more than a community? Get your own server", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.", "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.", "Featured Rooms:": "Featured Rooms:", diff --git a/src/utils/HostingLink.js b/src/utils/HostingLink.js new file mode 100644 index 0000000000..7efd8b6f96 --- /dev/null +++ b/src/utils/HostingLink.js @@ -0,0 +1,37 @@ +/* +Copyright 2019 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. +*/ + +import url from 'url'; +import qs from 'qs'; + +import SdkConfig from '../SdkConfig'; + +export function getHostingLink(campaign) { + const hostingLink = SdkConfig.get().hosting_signup_link; + if (!hostingLink) return null; + if (!campaign) return hostingLink; + + try { + const hostingUrl = url.parse(hostingLink); + const params = qs.parse(hostingUrl.query); + params.utm_campaign = campaign; + hostingUrl.search = undefined; + hostingUrl.query = params; + return hostingUrl.format(); + } catch (e) { + return hostingLink; + } +}