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 {
min-height: 531px;
max-height: 600px;
width: 580px;
.mx_HostSignupDialog_info {
text-align: center;
.mx_HostSignupDialog_content {
margin-top: 64px;
margin-bottom: 45px;
.mx_HostSignupDialog_content_top {
margin-bottom: 24px;
}
.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 {
@ -34,21 +106,3 @@ limitations under the License.
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 {UIFeature} from "../../settings/UIFeature";
import HostSignupAction from "./HostSignupAction";
import {IHostSignupConfig} from "../views/dialogs/HostSignupDialogTypes";
interface IProps {
isMinimized: boolean;
@ -274,7 +275,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
let topSection;
const signupLink = getHostingLink("user-context-menu");
const hostSignupConfig = SdkConfig.get().host_signup;
const hostSignupConfig: IHostSignupConfig = SdkConfig.get().hostSignup;
if (MatrixClientPeg.get().isGuest()) {
topSection = (
<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) {
let hostSignupAction;
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.
const hostSignupDomains = hostSignupConfig.domains || [];
const mxDomain = MatrixClientPeg.get().getDomain();

View File

@ -15,6 +15,7 @@ limitations under the License.
*/
import * as React from "react";
import AccessibleButton from "../elements/AccessibleButton";
import Modal from "../../../Modal";
import PersistedElement from "../elements/PersistedElement";
import QuestionDialog from './QuestionDialog';
@ -23,7 +24,12 @@ import {_t} from "../../../languageHandler";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {HostSignupStore} from "../../../stores/HostSignupStore";
import {OwnProfileStore} from "../../../stores/OwnProfileStore";
import {IPostmessage, IPostmessageResponseData, PostmessageAction} from "./HostSignupDialogTypes";
import {
IHostSignupConfig,
IPostmessage,
IPostmessageResponseData,
PostmessageAction,
} from "./HostSignupDialogTypes";
interface IProps {}
@ -32,11 +38,12 @@ interface IState {
error: string;
loadIframe: boolean;
minimized: boolean;
termsAccepted: boolean;
}
export default class HostSignupDialog extends React.PureComponent<IProps, IState> {
private iframeRef: React.RefObject<HTMLIFrameElement> = React.createRef();
private readonly hostSignupSetupUrl: string;
private readonly config: IHostSignupConfig;
constructor(props: IProps) {
super(props);
@ -46,13 +53,14 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
error: null,
loadIframe: false,
minimized: false,
termsAccepted: false,
};
this.hostSignupSetupUrl = SdkConfig.get().host_signup.url;
this.config = SdkConfig.get().hostSignup;
}
private messageHandler = async (message: IPostmessage) => {
if (!this.hostSignupSetupUrl.startsWith(message.origin)) {
if (!this.config.url.startsWith(message.origin)) {
return;
}
switch (message.data.action) {
@ -73,14 +81,16 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
});
break;
case PostmessageAction.CloseDialog:
return this.onFinished(true);
return this.closeDialog();
}
}
private maximizeDialog = () => {
this.setState({
minimized: false,
});
if (this.state.minimized) {
this.setState({
minimized: false,
});
}
}
private minimizeDialog = () => {
@ -97,8 +107,8 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
return HostSignupStore.instance.setHostSignupActive(false);
}
private onFinished = async (result: boolean) => {
if (result || this.state.completed) {
private onCloseClick = async () => {
if (this.state.completed) {
// We're done, close
return this.closeDialog();
} else {
@ -121,7 +131,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
}
private sendMessage = (message: IPostmessageResponseData) => {
this.iframeRef.current.contentWindow.postMessage(message, this.hostSignupSetupUrl);
this.iframeRef.current.contentWindow.postMessage(message, this.config.url);
}
private async sendAccountDetails() {
@ -141,6 +151,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
openIdToken: openIdToken.access_token,
serverName: await MatrixClientPeg.get().getDomain(),
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() {
if (HostSignupStore.instance.isHostSignupActive) {
// 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">
<PersistedElement key="host_signup" persistKey="host_signup">
<div className={this.state.minimized ? "" : "mx_Dialog_wrapper"}>
<div className={
this.state.minimized ? "mx_HostSignupDialog_minimized" : "mx_HostSignupDialog mx_Dialog"
<div className={["mx_Dialog",
this.state.minimized ? "mx_HostSignupDialog_minimized" : "mx_HostSignupDialog"].join(" ")
}>
{this.state.loadIframe &&
<iframe
src={this.hostSignupSetupUrl}
ref={this.iframeRef}
sandbox="allow-forms allow-scripts allow-same-origin"
/>
<>
{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
src={this.config.url}
ref={this.iframeRef}
sandbox="allow-forms allow-scripts allow-same-origin"
/>
</>
}
{!this.state.loadIframe &&
<div className="mx_HostSignupDialog_info">
{this.state.minimized &&
<button onClick={this.maximizeDialog}>Maximize</button>
{this.config.info.image &&
<img
alt={this.config.info.image.alt}
src={this.config.info.image.src}
/>
}
<img
alt="image of planet"
src={require("../../../../res/img/host_signup.png")}
/>
<div className="mx_HostSignupDialog_content">
<h1>Unlock the power of Element</h1>
<p>
Congratulations! You taken your first steps into unlocking the full&nbsp;
power of the Element app. In a few minutes, you'll be able to&nbsp;
see how powerful our&nbsp;
Matrix services are and take control of your conversation data.
</p>
<div className="mx_HostSignupDialog_content_top">
<h1 className="mx_HostSignupDialog_text_dark">
{this.config.info.title}
</h1>
{this.config.info.additionalParagraphs &&
<div className="mx_HostSignupDialog_paragraphs">
{this.config.info.additionalParagraphs.map((para, index) => {
return <p className="mx_HostSignupDialog_text_light" key={index}>
{para}
</p>;
})}
</div>
}
{this.config.info.additionalInfoLink &&
<p><small>
<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>
<button onClick={this.closeDialog}>Maybe later</button>
<button onClick={this.loadIframe} className="mx_Dialog_primary">
Lets get started
<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 onClick={this.minimizeDialog}>Minimize</button>
</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>
}
{this.state.error &&
@ -208,7 +289,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
}
</div>
{!this.state.minimized &&
<div className="mx_Dialog_background" />
<div className="mx_Dialog_background mx_HostSignupDialog_background" />
}
</div>
</PersistedElement>

View File

@ -29,6 +29,7 @@ interface IAccountData {
openIdToken: string;
serverName: string;
userLocalpart: string;
termsAccepted: boolean;
}
export interface IPostmessageRequestData {
@ -44,3 +45,43 @@ export interface IPostmessage {
data: IPostmessageRequestData;
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;
}