From 417de0cac7adbcd0aaadc6a563a3199131950d2b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 19 Aug 2019 22:59:33 -0600 Subject: [PATCH] Add an inline terms agreement component Handles agreement of terms in an inline way. --- res/css/_components.scss | 1 + .../views/terms/_InlineTermsAgreement.scss | 45 +++++++ .../views/terms/InlineTermsAgreement.js | 119 ++++++++++++++++++ src/i18n/strings/en_EN.json | 2 + 4 files changed, 167 insertions(+) create mode 100644 res/css/views/terms/_InlineTermsAgreement.scss create mode 100644 src/components/views/terms/InlineTermsAgreement.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 579369a509..b8811c742f 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -180,6 +180,7 @@ @import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss"; @import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss"; @import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss"; +@import "./views/terms/_InlineTermsAgreement.scss"; @import "./views/verification/_VerificationShowSas.scss"; @import "./views/voip/_CallView.scss"; @import "./views/voip/_IncomingCallbox.scss"; diff --git a/res/css/views/terms/_InlineTermsAgreement.scss b/res/css/views/terms/_InlineTermsAgreement.scss new file mode 100644 index 0000000000..e00dcf31d1 --- /dev/null +++ b/res/css/views/terms/_InlineTermsAgreement.scss @@ -0,0 +1,45 @@ +/* +Copyright 2019 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. +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_InlineTermsAgreement_cbContainer { + margin-bottom: 10px; + font-size: 14px; + + a { + color: $accent-color; + text-decoration: none; + } + + .mx_InlineTermsAgreement_checkbox { + margin-top: 10px; + + input { + vertical-align: text-bottom; + } + } +} + +.mx_InlineTermsAgreement_link { + display: inline-block; + mask-image: url('$(res)/img/external-link.svg'); + background-color: $accent-color; + mask-repeat: no-repeat; + mask-size: contain; + width: 12px; + height: 12px; + margin-left: 3px; + vertical-align: middle; +} diff --git a/src/components/views/terms/InlineTermsAgreement.js b/src/components/views/terms/InlineTermsAgreement.js new file mode 100644 index 0000000000..c88612dacb --- /dev/null +++ b/src/components/views/terms/InlineTermsAgreement.js @@ -0,0 +1,119 @@ +/* +Copyright 2019 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. +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 React from "react"; +import PropTypes from "prop-types"; +import {_t, pickBestLanguage} from "../../../languageHandler"; +import sdk from "../../../.."; + +export default class InlineTermsAgreement extends React.Component { + static propTypes = { + policiesAndServicePairs: PropTypes.array.isRequired, // array of service/policy pairs + agreedUrls: PropTypes.array.isRequired, // array of URLs the user has accepted + onFinished: PropTypes.func.isRequired, // takes an argument of accepted URLs + introElement: PropTypes.node, + }; + + constructor() { + super(); + + this.state = { + policies: [], + busy: false, + }; + } + + componentDidMount() { + // Build all the terms the user needs to accept + const policies = []; // { checked, url, name } + for (const servicePolicies of this.props.policiesAndServicePairs) { + const availablePolicies = Object.values(servicePolicies.policies); + for (const policy of availablePolicies) { + const language = pickBestLanguage(Object.keys(policy).filter(p => p !== 'version')); + const renderablePolicy = { + checked: false, + url: policy[language].url, + name: policy[language].name, + }; + policies.push(renderablePolicy); + } + } + + this.setState({policies}); + } + + _togglePolicy = (index) => { + const policies = JSON.parse(JSON.stringify(this.state.policies)); // deep & cheap clone + policies[index].checked = !policies[index].checked; + this.setState({policies}); + }; + + _onContinue = () => { + const hasUnchecked = !!this.state.policies.some(p => !p.checked); + if (hasUnchecked) return; + + this.setState({busy: true}); + this.props.onFinished(this.state.policies.map(p => p.url)); + }; + + _renderCheckboxes() { + const rendered = []; + for (let i = 0; i < this.state.policies.length; i++) { + const policy = this.state.policies[i]; + const introText = _t( + "Accept to continue:", {}, { + policyLink: () => { + return ( + + {policy.name} + + + ); + }, + }, + ); + rendered.push( +
+
{introText}
+
+ this._togglePolicy(i)} checked={policy.checked} /> + {_t("Accept")} +
+
+ ); + } + return rendered; + } + + render() { + const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton"); + const hasUnchecked = !!this.state.policies.some(p => !p.checked); + + return ( +
+ {this.props.introElement} + {this._renderCheckboxes()} + + {_t("Continue")} + +
+ ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d38965bea4..469a31bb43 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -457,6 +457,7 @@ "Headphones": "Headphones", "Folder": "Folder", "Pin": "Pin", + "Accept to continue:": "Accept to continue:", "Failed to upload profile picture!": "Failed to upload profile picture!", "Upload new:": "Upload new:", "No display name": "No display name", @@ -582,6 +583,7 @@ "Set a new account password...": "Set a new account password...", "Language and region": "Language and region", "Theme": "Theme", + "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.", "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account",