pull/21833/head
Jason Robinson 2021-01-15 15:32:30 +02:00
parent d6af5b3bbd
commit e80dba9414
4 changed files with 238 additions and 61 deletions

View File

@ -15,16 +15,88 @@ limitations under the License.
*/ */
.mx_HostSignupDialog { .mx_HostSignupDialog {
min-height: 531px;
max-height: 600px; max-height: 600px;
width: 580px; width: 580px;
.mx_HostSignupDialog_info { .mx_HostSignupDialog_info {
text-align: center; text-align: center;
.mx_HostSignupDialog_content { .mx_HostSignupDialog_content_top {
margin-top: 64px; margin-bottom: 24px;
margin-bottom: 45px;
} }
.mx_HostSignupDialog_paragraphs {
text-align: left;
padding-left: 25%;
padding-right: 25%;
}
.mx_HostSignupDialog_buttons {
margin-bottom: 24px;
button {
padding: 12px;
}
}
}
iframe {
width: 100%;
height: 100%;
border: none;
background-color: #fff;
min-height: 500px;
}
}
.mx_HostSignupDialog_text_dark {
color: $primary-fg-color;
}
.mx_HostSignupDialog_text_light {
color: $secondary-fg-color;
}
.mx_HostSignup_maximize_button {
mask: url('$(res)/img/feather-customised/widget/maximise.svg');
mask-repeat: no-repeat;
mask-position: center;
mask-size: cover;
width: 14px;
height: 14px;
background-color: $dialog-close-fg-color;
cursor: pointer;
position: absolute;
top: 10px;
right: 10px;
}
.mx_HostSignup_persisted {
width: 580px;
height: 600px;
top: 0;
left: 0;
position: fixed;
display: none;
}
.mx_HostSignupDialog_minimized {
position: fixed;
bottom: 80px;
right: 26px;
width: 314px;
height: 217px;
overflow: hidden;
&.mx_Dialog {
padding: 12px;
}
.mx_Dialog_title {
text-align: left !important;
padding-left: 20px;
font-size: $font-15px;
} }
iframe { iframe {
@ -34,21 +106,3 @@ limitations under the License.
background-color: #fff; background-color: #fff;
} }
} }
.mx_HostSignup_persisted {
width: 580px;
height: 600px;
top: 0;
left: 0;
position: fixed;
}
.mx_HostSignupDialog_minimized {
position: fixed;
bottom: 100px;
right: 100px;
width: 200px;
height: 120px;
overflow: hidden;
z-index: 6000;
}

View File

@ -52,6 +52,7 @@ import ErrorDialog from "../views/dialogs/ErrorDialog";
import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototypeDialog"; import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototypeDialog";
import {UIFeature} from "../../settings/UIFeature"; import {UIFeature} from "../../settings/UIFeature";
import HostSignupAction from "./HostSignupAction"; import HostSignupAction from "./HostSignupAction";
import {IHostSignupConfig} from "../views/dialogs/HostSignupDialogTypes";
interface IProps { interface IProps {
isMinimized: boolean; isMinimized: boolean;
@ -274,7 +275,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
let topSection; let topSection;
const signupLink = getHostingLink("user-context-menu"); const signupLink = getHostingLink("user-context-menu");
const hostSignupConfig = SdkConfig.get().host_signup; const hostSignupConfig: IHostSignupConfig = SdkConfig.get().hostSignup;
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
topSection = ( topSection = (
<div className="mx_UserMenu_contextMenu_header mx_UserMenu_contextMenu_guestPrompts"> <div className="mx_UserMenu_contextMenu_header mx_UserMenu_contextMenu_guestPrompts">
@ -297,7 +298,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
} else if (signupLink || hostSignupConfig) { } else if (signupLink || hostSignupConfig) {
let hostSignupAction; let hostSignupAction;
if (hostSignupConfig && hostSignupConfig.url) { if (hostSignupConfig && hostSignupConfig.url) {
// If host_signup.domains is set to a non-empty array, only show // If hostSignup.domains is set to a non-empty array, only show
// dialog if the user is on the domain or a subdomain. // dialog if the user is on the domain or a subdomain.
const hostSignupDomains = hostSignupConfig.domains || []; const hostSignupDomains = hostSignupConfig.domains || [];
const mxDomain = MatrixClientPeg.get().getDomain(); const mxDomain = MatrixClientPeg.get().getDomain();

View File

@ -15,6 +15,7 @@ limitations under the License.
*/ */
import * as React from "react"; import * as React from "react";
import AccessibleButton from "../elements/AccessibleButton";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import PersistedElement from "../elements/PersistedElement"; import PersistedElement from "../elements/PersistedElement";
import QuestionDialog from './QuestionDialog'; import QuestionDialog from './QuestionDialog';
@ -23,7 +24,12 @@ import {_t} from "../../../languageHandler";
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {HostSignupStore} from "../../../stores/HostSignupStore"; import {HostSignupStore} from "../../../stores/HostSignupStore";
import {OwnProfileStore} from "../../../stores/OwnProfileStore"; import {OwnProfileStore} from "../../../stores/OwnProfileStore";
import {IPostmessage, IPostmessageResponseData, PostmessageAction} from "./HostSignupDialogTypes"; import {
IHostSignupConfig,
IPostmessage,
IPostmessageResponseData,
PostmessageAction,
} from "./HostSignupDialogTypes";
interface IProps {} interface IProps {}
@ -32,11 +38,12 @@ interface IState {
error: string; error: string;
loadIframe: boolean; loadIframe: boolean;
minimized: boolean; minimized: boolean;
termsAccepted: boolean;
} }
export default class HostSignupDialog extends React.PureComponent<IProps, IState> { export default class HostSignupDialog extends React.PureComponent<IProps, IState> {
private iframeRef: React.RefObject<HTMLIFrameElement> = React.createRef(); private iframeRef: React.RefObject<HTMLIFrameElement> = React.createRef();
private readonly hostSignupSetupUrl: string; private readonly config: IHostSignupConfig;
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -46,13 +53,14 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
error: null, error: null,
loadIframe: false, loadIframe: false,
minimized: false, minimized: false,
termsAccepted: false,
}; };
this.hostSignupSetupUrl = SdkConfig.get().host_signup.url; this.config = SdkConfig.get().hostSignup;
} }
private messageHandler = async (message: IPostmessage) => { private messageHandler = async (message: IPostmessage) => {
if (!this.hostSignupSetupUrl.startsWith(message.origin)) { if (!this.config.url.startsWith(message.origin)) {
return; return;
} }
switch (message.data.action) { switch (message.data.action) {
@ -73,15 +81,17 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
}); });
break; break;
case PostmessageAction.CloseDialog: case PostmessageAction.CloseDialog:
return this.onFinished(true); return this.closeDialog();
} }
} }
private maximizeDialog = () => { private maximizeDialog = () => {
if (this.state.minimized) {
this.setState({ this.setState({
minimized: false, minimized: false,
}); });
} }
}
private minimizeDialog = () => { private minimizeDialog = () => {
this.setState({ this.setState({
@ -97,8 +107,8 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
return HostSignupStore.instance.setHostSignupActive(false); return HostSignupStore.instance.setHostSignupActive(false);
} }
private onFinished = async (result: boolean) => { private onCloseClick = async () => {
if (result || this.state.completed) { if (this.state.completed) {
// We're done, close // We're done, close
return this.closeDialog(); return this.closeDialog();
} else { } else {
@ -121,7 +131,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
} }
private sendMessage = (message: IPostmessageResponseData) => { private sendMessage = (message: IPostmessageResponseData) => {
this.iframeRef.current.contentWindow.postMessage(message, this.hostSignupSetupUrl); this.iframeRef.current.contentWindow.postMessage(message, this.config.url);
} }
private async sendAccountDetails() { private async sendAccountDetails() {
@ -141,6 +151,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
openIdToken: openIdToken.access_token, openIdToken: openIdToken.access_token,
serverName: await MatrixClientPeg.get().getDomain(), serverName: await MatrixClientPeg.get().getDomain(),
userLocalpart: await MatrixClientPeg.get().getUserIdLocalpart(), userLocalpart: await MatrixClientPeg.get().getUserIdLocalpart(),
termsAccepted: this.state.termsAccepted,
}, },
}); });
} }
@ -152,6 +163,25 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
}); });
} }
private onStartClick = () => {
Modal.createDialog(
QuestionDialog,
{
title: this.config.termsDialog.title,
description: this.config.termsDialog.text,
button: this.config.termsDialog.acceptText,
onFinished: result => {
if (result) {
this.setState({
termsAccepted: true,
});
this.loadIframe();
}
},
},
);
}
public componentWillUnmount() { public componentWillUnmount() {
if (HostSignupStore.instance.isHostSignupActive) { if (HostSignupStore.instance.isHostSignupActive) {
// Run the close dialog actions if we're still active, otherwise good to go // Run the close dialog actions if we're still active, otherwise good to go
@ -164,41 +194,92 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
<div className="mx_HostSignup_persisted"> <div className="mx_HostSignup_persisted">
<PersistedElement key="host_signup" persistKey="host_signup"> <PersistedElement key="host_signup" persistKey="host_signup">
<div className={this.state.minimized ? "" : "mx_Dialog_wrapper"}> <div className={this.state.minimized ? "" : "mx_Dialog_wrapper"}>
<div className={ <div className={["mx_Dialog",
this.state.minimized ? "mx_HostSignupDialog_minimized" : "mx_HostSignupDialog mx_Dialog" this.state.minimized ? "mx_HostSignupDialog_minimized" : "mx_HostSignupDialog"].join(" ")
}> }>
{this.state.loadIframe && {this.state.loadIframe &&
<>
{this.state.minimized &&
<div className="mx_Dialog_header mx_Dialog_headerWithButton">
<div className="mx_Dialog_title">
{this.config.minimizedDialogTitle}
</div>
<AccessibleButton
className="mx_HostSignup_maximize_button"
onClick={this.maximizeDialog}
aria-label={_t("Maximize dialog")}
/>
</div>
}
{!this.state.minimized &&
<div className="mx_Dialog_header mx_Dialog_headerWithCancel">
<AccessibleButton
onClick={this.onCloseClick} className="mx_Dialog_cancelButton"
aria-label={_t("Close dialog")}
/>
</div>
}
<iframe <iframe
src={this.hostSignupSetupUrl} src={this.config.url}
ref={this.iframeRef} ref={this.iframeRef}
sandbox="allow-forms allow-scripts allow-same-origin" sandbox="allow-forms allow-scripts allow-same-origin"
/> />
</>
} }
{!this.state.loadIframe && {!this.state.loadIframe &&
<div className="mx_HostSignupDialog_info"> <div className="mx_HostSignupDialog_info">
{this.state.minimized && {this.config.info.image &&
<button onClick={this.maximizeDialog}>Maximize</button>
}
<img <img
alt="image of planet" alt={this.config.info.image.alt}
src={require("../../../../res/img/host_signup.png")} src={this.config.info.image.src}
/> />
<div className="mx_HostSignupDialog_content"> }
<h1>Unlock the power of Element</h1> <div className="mx_HostSignupDialog_content_top">
<p> <h1 className="mx_HostSignupDialog_text_dark">
Congratulations! You taken your first steps into unlocking the full&nbsp; {this.config.info.title}
power of the Element app. In a few minutes, you'll be able to&nbsp; </h1>
see how powerful our&nbsp; {this.config.info.additionalParagraphs &&
Matrix services are and take control of your conversation data. <div className="mx_HostSignupDialog_paragraphs">
</p> {this.config.info.additionalParagraphs.map((para, index) => {
return <p className="mx_HostSignupDialog_text_light" key={index}>
{para}
</p>;
})}
</div> </div>
<div> }
<button onClick={this.closeDialog}>Maybe later</button> {this.config.info.additionalInfoLink &&
<button onClick={this.loadIframe} className="mx_Dialog_primary"> <p><small>
Lets get started <a href={this.config.info.additionalInfoLink.href} target="_blank"
rel="noopener noreferrer"
title={this.config.info.additionalInfoLink.text}
>
{this.config.info.additionalInfoLink.text}
</a>
</small></p>
}
</div>
<div className="mx_HostSignupDialog_buttons">
{/*TODO: what about accessibility? the signup flow is possibly not reader optimized*/}
<button onClick={this.closeDialog}>{this.config.info.cancelText}</button>
<button onClick={this.onStartClick} className="mx_Dialog_primary">
{this.config.info.continueText}
</button> </button>
<button onClick={this.minimizeDialog}>Minimize</button>
</div> </div>
{this.config.info.footer &&
<div className="mx_HostSignupDialog_text_light">
<small>
<p>
{this.config.info.footer.image &&
<img
alt={this.config.info.footer.image.alt}
src={this.config.info.footer.image.src}
/>
}
{this.config.info.footer.text}
</p>
</small>
</div>
}
</div> </div>
} }
{this.state.error && {this.state.error &&
@ -208,7 +289,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
} }
</div> </div>
{!this.state.minimized && {!this.state.minimized &&
<div className="mx_Dialog_background" /> <div className="mx_Dialog_background mx_HostSignupDialog_background" />
} }
</div> </div>
</PersistedElement> </PersistedElement>

View File

@ -29,6 +29,7 @@ interface IAccountData {
openIdToken: string; openIdToken: string;
serverName: string; serverName: string;
userLocalpart: string; userLocalpart: string;
termsAccepted: boolean;
} }
export interface IPostmessageRequestData { export interface IPostmessageRequestData {
@ -44,3 +45,43 @@ export interface IPostmessage {
data: IPostmessageRequestData; data: IPostmessageRequestData;
origin: string; origin: string;
} }
interface IImage {
alt: string;
src: string;
}
interface ILink {
href: string;
text: string;
}
interface IInfoFooter {
image: IImage;
text: string;
}
interface IHostSignupInfoConfig {
additionalInfoLink?: ILink;
additionalParagraphs?: Array<string>;
cancelText: string;
continueText: string;
footer?: IInfoFooter;
image?: IImage;
title: string;
}
interface IHostSignupTermsDialogConfig {
acceptText: string;
termsDocuments: Array<ILink>;
text: string;
title: string;
}
export interface IHostSignupConfig {
domains: Array<string>;
info: IHostSignupInfoConfig;
minimizedDialogTitle: string;
termsDialog: IHostSignupTermsDialogConfig;
url: string;
}