From 756c5ac0d3c9b685f95cc755ad098cef74e0c517 Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <i.kamalov@adguard.com>
Date: Tue, 19 Mar 2019 16:19:53 +0300
Subject: [PATCH] + client: added setup guide page and DNS addresses popover

Closes #605
---
 client/src/__locales/en.json               |  4 +-
 client/src/components/App/index.js         |  4 +-
 client/src/components/Header/Header.css    |  7 ++
 client/src/components/Header/Menu.js       |  7 +-
 client/src/components/Header/Version.js    | 23 ++++--
 client/src/components/Header/index.js      | 12 ++--
 client/src/components/SetupGuide/Guide.css | 15 ++++
 client/src/components/SetupGuide/index.js  | 46 ++++++++++++
 client/src/components/ui/Guide.js          | 83 ++++++++++++++++++++++
 client/src/components/ui/Popover.css       | 46 ++++++++++++
 client/src/containers/SetupGuide.js        | 14 ++++
 client/src/install/Setup/Devices.js        | 75 +------------------
 client/src/reducers/index.js               |  7 +-
 13 files changed, 251 insertions(+), 92 deletions(-)
 create mode 100644 client/src/components/SetupGuide/Guide.css
 create mode 100644 client/src/components/SetupGuide/index.js
 create mode 100644 client/src/components/ui/Guide.js
 create mode 100644 client/src/containers/SetupGuide.js

diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 92540ea6..36b78e79 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -249,5 +249,7 @@
     "update_announcement": "AdGuard Home {{version}} is now available! <0>Click here</0> for more info.",
     "upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers",
     "bootstrap_dns": "Bootstrap DNS servers",
-    "bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DOH/DOT resolvers you specify as upstreams."
+    "bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DOH/DOT resolvers you specify as upstreams.",
+    "setup_guide": "Setup guide",
+    "dns_addresses": "DNS addresses"
 }
diff --git a/client/src/components/App/index.js b/client/src/components/App/index.js
index 91c5e512..a2676dd0 100644
--- a/client/src/components/App/index.js
+++ b/client/src/components/App/index.js
@@ -14,8 +14,9 @@ import Dashboard from '../../containers/Dashboard';
 import Settings from '../../containers/Settings';
 import Filters from '../../containers/Filters';
 import Logs from '../../containers/Logs';
-import Footer from '../ui/Footer';
+import SetupGuide from '../../containers/SetupGuide';
 import Toasts from '../Toasts';
+import Footer from '../ui/Footer';
 import Status from '../ui/Status';
 import UpdateTopline from '../ui/UpdateTopline';
 import EncryptionTopline from '../ui/EncryptionTopline';
@@ -86,6 +87,7 @@ class App extends Component {
                                 <Route path="/settings" component={Settings} />
                                 <Route path="/filters" component={Filters} />
                                 <Route path="/logs" component={Logs} />
+                                <Route path="/guide" component={SetupGuide} />
                             </Fragment>
                         }
                     </div>
diff --git a/client/src/components/Header/Header.css b/client/src/components/Header/Header.css
index dbbcf08d..a8273e78 100644
--- a/client/src/components/Header/Header.css
+++ b/client/src/components/Header/Header.css
@@ -76,6 +76,13 @@
     font-weight: 600;
 }
 
+.nav-version__link {
+    position: relative;
+    display: inline-block;
+    border-bottom: 1px dashed #495057;
+    cursor: pointer;
+}
+
 .header-brand-img {
     height: 32px;
 }
diff --git a/client/src/components/Header/Menu.js b/client/src/components/Header/Menu.js
index ef465378..2800d768 100644
--- a/client/src/components/Header/Menu.js
+++ b/client/src/components/Header/Menu.js
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
 import enhanceWithClickOutside from 'react-click-outside';
 import classnames from 'classnames';
 import { Trans, withNamespaces } from 'react-i18next';
-import { REPOSITORY } from '../../helpers/constants';
 
 class Menu extends Component {
     handleClickOutside = () => {
@@ -56,10 +55,10 @@ class Menu extends Component {
                             </NavLink>
                         </li>
                         <li className="nav-item">
-                            <a href={`${REPOSITORY.URL}/wiki`} className="nav-link" target="_blank" rel="noopener noreferrer">
+                            <NavLink to="/guide" href="/guide" className="nav-link">
                                 <svg className="nav-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#66b574" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg>
-                                <Trans>faq</Trans>
-                            </a>
+                                <Trans>setup_guide</Trans>
+                            </NavLink>
                         </li>
                     </ul>
                 </div>
diff --git a/client/src/components/Header/Version.js b/client/src/components/Header/Version.js
index 0faedbcc..be2158e9 100644
--- a/client/src/components/Header/Version.js
+++ b/client/src/components/Header/Version.js
@@ -2,24 +2,35 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Trans, withNamespaces } from 'react-i18next';
 
+import { getDnsAddress } from '../../helpers/helpers';
+
 function Version(props) {
-    const { dnsVersion, dnsAddress, dnsPort } = props;
+    const { dnsVersion, dnsAddresses, dnsPort } = props;
     return (
         <div className="nav-version">
             <div className="nav-version__text">
                 <Trans>version</Trans>: <span className="nav-version__value">{dnsVersion}</span>
             </div>
-            <div className="nav-version__text">
-                <Trans>address</Trans>: <span className="nav-version__value">{dnsAddress}:{dnsPort}</span>
+            <div className="nav-version__link">
+                <div className="popover__trigger popover__trigger--address">
+                    <Trans>dns_addresses</Trans>
+                </div>
+                <div className="popover__body popover__body--address">
+                    <div className="popover__list">
+                        {dnsAddresses
+                            .map(ip => <li key={ip}>{getDnsAddress(ip, dnsPort)}</li>)
+                        }
+                    </div>
+                </div>
             </div>
         </div>
     );
 }
 
 Version.propTypes = {
-    dnsVersion: PropTypes.string,
-    dnsAddress: PropTypes.string,
-    dnsPort: PropTypes.number,
+    dnsVersion: PropTypes.string.isRequired,
+    dnsAddresses: PropTypes.array.isRequired,
+    dnsPort: PropTypes.number.isRequired,
 };
 
 export default withNamespaces()(Version);
diff --git a/client/src/components/Header/index.js b/client/src/components/Header/index.js
index ecc1a2af..e2d47fe6 100644
--- a/client/src/components/Header/index.js
+++ b/client/src/components/Header/index.js
@@ -56,11 +56,13 @@ class Header extends Component {
                             toggleMenuOpen={this.toggleMenuOpen}
                             closeMenu={this.closeMenu}
                         />
-                        <div className="col col-sm-6 col-lg-3">
-                            <Version
-                                { ...this.props.dashboard }
-                            />
-                        </div>
+                        {!dashboard.processing &&
+                            <div className="col col-sm-6 col-lg-3">
+                                <Version
+                                    { ...this.props.dashboard }
+                                />
+                            </div>
+                        }
                     </div>
                 </div>
             </div>
diff --git a/client/src/components/SetupGuide/Guide.css b/client/src/components/SetupGuide/Guide.css
new file mode 100644
index 00000000..821f1798
--- /dev/null
+++ b/client/src/components/SetupGuide/Guide.css
@@ -0,0 +1,15 @@
+.guide {
+    max-width: 768px;
+    margin: 0 auto;
+}
+
+.guide__title {
+    margin-bottom: 10px;
+    font-size: 17px;
+    font-weight: 700;
+}
+
+.guide__desc {
+    margin-bottom: 20px;
+    font-size: 15px;
+}
diff --git a/client/src/components/SetupGuide/index.js b/client/src/components/SetupGuide/index.js
new file mode 100644
index 00000000..bea2bbb1
--- /dev/null
+++ b/client/src/components/SetupGuide/index.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Trans, withNamespaces } from 'react-i18next';
+
+import { getDnsAddress } from '../../helpers/helpers';
+
+import Guide from '../ui/Guide';
+import Card from '../ui/Card';
+import PageTitle from '../ui/PageTitle';
+import './Guide.css';
+
+const SetupGuide = ({
+    t,
+    dashboard: {
+        dnsAddresses,
+        dnsPort,
+    },
+}) => (
+    <div className="guide">
+        <PageTitle title={t('setup_guide')} />
+        <Card>
+            <div className="guide__title">
+                <Trans>install_devices_title</Trans>
+            </div>
+            <div className="guide__desc">
+                <Trans>install_devices_desc</Trans>
+                <div className="mt-1">
+                    <Trans>install_devices_address</Trans>:
+                </div>
+                <div className="mt-2 font-weight-bold">
+                    {dnsAddresses
+                        .map(ip => <li key={ip}>{getDnsAddress(ip, dnsPort)}</li>)
+                    }
+                </div>
+            </div>
+            <Guide />
+        </Card>
+    </div>
+);
+
+SetupGuide.propTypes = {
+    dashboard: PropTypes.object.isRequired,
+    t: PropTypes.func.isRequired,
+};
+
+export default withNamespaces()(SetupGuide);
diff --git a/client/src/components/ui/Guide.js b/client/src/components/ui/Guide.js
new file mode 100644
index 00000000..9e89f7ec
--- /dev/null
+++ b/client/src/components/ui/Guide.js
@@ -0,0 +1,83 @@
+import React from 'react';
+import { Trans, withNamespaces } from 'react-i18next';
+
+import Tabs from '../ui/Tabs';
+import Icons from '../ui/Icons';
+
+const Guide = () => (
+    <div>
+        <Icons />
+        <Tabs>
+            <div label="Router">
+                <div className="tab__title">
+                    <Trans>install_devices_router</Trans>
+                </div>
+                <div className="tab__text">
+                    <p><Trans>install_devices_router_desc</Trans></p>
+                    <ol>
+                        <li><Trans>install_devices_router_list_1</Trans></li>
+                        <li><Trans>install_devices_router_list_2</Trans></li>
+                        <li><Trans>install_devices_router_list_3</Trans></li>
+                    </ol>
+                </div>
+            </div>
+            <div label="Windows">
+                <div className="tab__title">
+                    Windows
+                </div>
+                <div className="tab__text">
+                    <ol>
+                        <li><Trans>install_devices_windows_list_1</Trans></li>
+                        <li><Trans>install_devices_windows_list_2</Trans></li>
+                        <li><Trans>install_devices_windows_list_3</Trans></li>
+                        <li><Trans>install_devices_windows_list_4</Trans></li>
+                        <li><Trans>install_devices_windows_list_5</Trans></li>
+                        <li><Trans>install_devices_windows_list_6</Trans></li>
+                    </ol>
+                </div>
+            </div>
+            <div label="macOS">
+                <div className="tab__title">
+                    macOS
+                </div>
+                <div className="tab__text">
+                    <ol>
+                        <li><Trans>install_devices_macos_list_1</Trans></li>
+                        <li><Trans>install_devices_macos_list_2</Trans></li>
+                        <li><Trans>install_devices_macos_list_3</Trans></li>
+                        <li><Trans>install_devices_macos_list_4</Trans></li>
+                    </ol>
+                </div>
+            </div>
+            <div label="Android">
+                <div className="tab__title">
+                    Android
+                </div>
+                <div className="tab__text">
+                    <ol>
+                        <li><Trans>install_devices_android_list_1</Trans></li>
+                        <li><Trans>install_devices_android_list_2</Trans></li>
+                        <li><Trans>install_devices_android_list_3</Trans></li>
+                        <li><Trans>install_devices_android_list_4</Trans></li>
+                        <li><Trans>install_devices_android_list_5</Trans></li>
+                    </ol>
+                </div>
+            </div>
+            <div label="iOS">
+                <div className="tab__title">
+                    iOS
+                </div>
+                <div className="tab__text">
+                    <ol>
+                        <li><Trans>install_devices_ios_list_1</Trans></li>
+                        <li><Trans>install_devices_ios_list_2</Trans></li>
+                        <li><Trans>install_devices_ios_list_3</Trans></li>
+                        <li><Trans>install_devices_ios_list_4</Trans></li>
+                    </ol>
+                </div>
+            </div>
+        </Tabs>
+    </div>
+);
+
+export default withNamespaces()(Guide);
diff --git a/client/src/components/ui/Popover.css b/client/src/components/ui/Popover.css
index dc83e4cb..f7e23836 100644
--- a/client/src/components/ui/Popover.css
+++ b/client/src/components/ui/Popover.css
@@ -22,6 +22,16 @@
     height: 24px;
 }
 
+.popover__trigger--address {
+    top: 0;
+    margin: 0;
+    line-height: 1.2;
+}
+
+.popover__trigger--address:after {
+    display: none;
+}
+
 .popover__body {
     content: "";
     display: flex;
@@ -57,6 +67,38 @@
     border-top: 6px solid #585965;
 }
 
+.popover__body--address {
+    top: calc(100% + 10px);
+    right: 0;
+    left: initial;
+    bottom: initial;
+    z-index: 1;
+    min-width: 100px;
+    padding: 12px 18px;
+    font-weight: 700;
+    text-align: left;
+    white-space: nowrap;
+    transform: none;
+    cursor: default;
+}
+
+.popover__body--address:after {
+    top: -11px;
+    left: initial;
+    right: 40px;
+    border-top: 6px solid transparent;
+    border-bottom: 6px solid #585965;
+}
+
+.popover__body--address:before {
+    content: "";
+    position: absolute;
+    top: -7px;
+    left: 0;
+    width: 100%;
+    height: 10px;
+}
+
 .popover__trigger:hover + .popover__body,
 .popover__body:hover {
     visibility: visible;
@@ -73,6 +115,10 @@
     stroke: #66b574;
 }
 
+.popover__list--bold {
+    font-weight: 700;
+}
+
 .popover__list-title {
     margin-bottom: 3px;
 }
diff --git a/client/src/containers/SetupGuide.js b/client/src/containers/SetupGuide.js
new file mode 100644
index 00000000..1e7ecd12
--- /dev/null
+++ b/client/src/containers/SetupGuide.js
@@ -0,0 +1,14 @@
+import { connect } from 'react-redux';
+import * as actionCreators from '../actions';
+import SetupGuide from '../components/SetupGuide';
+
+const mapStateToProps = (state) => {
+    const { dashboard } = state;
+    const props = { dashboard };
+    return props;
+};
+
+export default connect(
+    mapStateToProps,
+    actionCreators,
+)(SetupGuide);
diff --git a/client/src/install/Setup/Devices.js b/client/src/install/Setup/Devices.js
index 4a5f7c13..f3755b3d 100644
--- a/client/src/install/Setup/Devices.js
+++ b/client/src/install/Setup/Devices.js
@@ -5,8 +5,7 @@ import { reduxForm, formValueSelector } from 'redux-form';
 import { Trans, withNamespaces } from 'react-i18next';
 import flow from 'lodash/flow';
 
-import Tabs from '../../components/ui/Tabs';
-import Icons from '../../components/ui/Icons';
+import Guide from '../../components/ui/Guide';
 import Controls from './Controls';
 import AddressList from './AddressList';
 
@@ -30,77 +29,7 @@ let Devices = props => (
                     />
                 </div>
             </div>
-            <Icons />
-            <Tabs>
-                <div label="Router">
-                    <div className="tab__title">
-                        <Trans>install_devices_router</Trans>
-                    </div>
-                    <div className="tab__text">
-                        <p><Trans>install_devices_router_desc</Trans></p>
-                        <ol>
-                            <li><Trans>install_devices_router_list_1</Trans></li>
-                            <li><Trans>install_devices_router_list_2</Trans></li>
-                            <li><Trans>install_devices_router_list_3</Trans></li>
-                        </ol>
-                    </div>
-                </div>
-                <div label="Windows">
-                    <div className="tab__title">
-                        Windows
-                    </div>
-                    <div className="tab__text">
-                        <ol>
-                            <li><Trans>install_devices_windows_list_1</Trans></li>
-                            <li><Trans>install_devices_windows_list_2</Trans></li>
-                            <li><Trans>install_devices_windows_list_3</Trans></li>
-                            <li><Trans>install_devices_windows_list_4</Trans></li>
-                            <li><Trans>install_devices_windows_list_5</Trans></li>
-                            <li><Trans>install_devices_windows_list_6</Trans></li>
-                        </ol>
-                    </div>
-                </div>
-                <div label="macOS">
-                    <div className="tab__title">
-                        macOS
-                    </div>
-                    <div className="tab__text">
-                        <ol>
-                            <li><Trans>install_devices_macos_list_1</Trans></li>
-                            <li><Trans>install_devices_macos_list_2</Trans></li>
-                            <li><Trans>install_devices_macos_list_3</Trans></li>
-                            <li><Trans>install_devices_macos_list_4</Trans></li>
-                        </ol>
-                    </div>
-                </div>
-                <div label="Android">
-                    <div className="tab__title">
-                        Android
-                    </div>
-                    <div className="tab__text">
-                        <ol>
-                            <li><Trans>install_devices_android_list_1</Trans></li>
-                            <li><Trans>install_devices_android_list_2</Trans></li>
-                            <li><Trans>install_devices_android_list_3</Trans></li>
-                            <li><Trans>install_devices_android_list_4</Trans></li>
-                            <li><Trans>install_devices_android_list_5</Trans></li>
-                        </ol>
-                    </div>
-                </div>
-                <div label="iOS">
-                    <div className="tab__title">
-                        iOS
-                    </div>
-                    <div className="tab__text">
-                        <ol>
-                            <li><Trans>install_devices_ios_list_1</Trans></li>
-                            <li><Trans>install_devices_ios_list_2</Trans></li>
-                            <li><Trans>install_devices_ios_list_3</Trans></li>
-                            <li><Trans>install_devices_ios_list_4</Trans></li>
-                        </ol>
-                    </div>
-                </div>
-            </Tabs>
+            <Guide />
         </div>
         <Controls />
     </div>
diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js
index 90ace4d6..a6daef82 100644
--- a/client/src/reducers/index.js
+++ b/client/src/reducers/index.js
@@ -48,7 +48,7 @@ const dashboard = handleActions({
             version,
             running,
             dns_port: dnsPort,
-            dns_address: dnsAddress,
+            dns_addresses: dnsAddresses,
             querylog_enabled: queryLogEnabled,
             upstream_dns: upstreamDns,
             bootstrap_dns: bootstrapDns,
@@ -63,7 +63,7 @@ const dashboard = handleActions({
             processing: false,
             dnsVersion: version,
             dnsPort,
-            dnsAddress,
+            dnsAddresses,
             queryLogEnabled,
             upstreamDns: upstreamDns.join('\n'),
             bootstrapDns: bootstrapDns.join('\n'),
@@ -181,6 +181,9 @@ const dashboard = handleActions({
     protectionEnabled: false,
     processingProtection: false,
     httpPort: 80,
+    dnsPort: 53,
+    dnsAddresses: [],
+    dnsVersion: '',
 });
 
 const queryLogs = handleActions({