From b81b8be2c2324d2049ddb4b3c279c5d2e4d684d8 Mon Sep 17 00:00:00 2001 From: "przemyslaw.wlodek" Date: Fri, 31 Dec 2021 17:00:29 +0100 Subject: [PATCH 01/76] [DDW-809] WIP Analytics agreement --- source/common/config/electron-store.config.js | 1 + source/renderer/app/App.js | 2 + source/renderer/app/Routes.js | 2 + .../renderer/app/actions/profile-actions.js | 1 + source/renderer/app/api/utils/localStorage.js | 6 + .../components/analytics/Analytics.stories.js | 68 ++++++++++ .../components/analytics/AnalyticsDIalog.scss | 62 +++++++++ .../components/analytics/AnalyticsDialog.js | 121 ++++++++++++++++++ .../components/widgets/forms/NormalSwitch.js | 3 +- .../app/containers/profile/AnalyticsPage.js | 46 +++++++ source/renderer/app/routes-config.js | 1 + source/renderer/app/stores/AppStore.js | 3 +- source/renderer/app/stores/ProfileStore.js | 50 ++++++++ storybook/stories/index.js | 4 + 14 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 source/renderer/app/components/analytics/Analytics.stories.js create mode 100644 source/renderer/app/components/analytics/AnalyticsDIalog.scss create mode 100644 source/renderer/app/components/analytics/AnalyticsDialog.js create mode 100644 source/renderer/app/containers/profile/AnalyticsPage.js diff --git a/source/common/config/electron-store.config.js b/source/common/config/electron-store.config.js index c18b83d727..52e29b3574 100644 --- a/source/common/config/electron-store.config.js +++ b/source/common/config/electron-store.config.js @@ -31,6 +31,7 @@ export const STORAGE_KEYS: { SMASH_SERVER: 'SMASH-SERVER', STAKING_INFO_WAS_OPEN: 'ALONZO-INFO-WAS-OPEN', TERMS_OF_USE_ACCEPTANCE: 'TERMS-OF-USE-ACCEPTANCE', + ANALYTICS_ACCEPTANCE: 'ANALYTICS-ACCEPTANCE', THEME: 'THEME', TOKEN_FAVORITES: 'TOKEN-FAVORITES', USER_DATE_FORMAT_ENGLISH: 'USER-DATE-FORMAT-ENGLISH', diff --git a/source/renderer/app/App.js b/source/renderer/app/App.js index 211f523247..68e5853036 100755 --- a/source/renderer/app/App.js +++ b/source/renderer/app/App.js @@ -20,6 +20,7 @@ import { DIALOGS } from '../../common/ipc/constants'; import type { StoresMap } from './stores/index'; import type { ActionsMap } from './actions/index'; import NewsFeedContainer from './containers/news/NewsFeedContainer'; +import AnalyticsDialog from './components/analytics/AnalyticsDialog'; @observer export default class App extends Component<{ @@ -70,6 +71,7 @@ export default class App extends Component<{ {mobxDevTools} + {/* */} {[ isActiveDialog(ABOUT) && , isActiveDialog(DAEDALUS_DIAGNOSTICS) && ( diff --git a/source/renderer/app/Routes.js b/source/renderer/app/Routes.js index caccb5b113..8048844b06 100644 --- a/source/renderer/app/Routes.js +++ b/source/renderer/app/Routes.js @@ -36,6 +36,7 @@ import WalletSettingsPage from './containers/wallet/WalletSettingsPage'; import WalletUtxoPage from './containers/wallet/WalletUtxoPage'; import VotingRegistrationPage from './containers/voting/VotingRegistrationPage'; import { IS_STAKING_INFO_PAGE_AVAILABLE } from './config/stakingConfig'; +import AnalyticsPage from './containers/profile/AnalyticsPage'; export const Routes = withRouter(() => ( @@ -51,6 +52,7 @@ export const Routes = withRouter(() => ( component={InitialSettingsPage} /> + = new Action(); acceptTermsOfUse: Action = new Action(); acceptDataLayerMigration: Action = new Action(); getLogs: Action = new Action(); diff --git a/source/renderer/app/api/utils/localStorage.js b/source/renderer/app/api/utils/localStorage.js index 1245aee667..fe0f361d72 100644 --- a/source/renderer/app/api/utils/localStorage.js +++ b/source/renderer/app/api/utils/localStorage.js @@ -174,6 +174,12 @@ export default class LocalStorageApi { setTermsOfUseAcceptance = (): Promise => LocalStorageApi.set(keys.TERMS_OF_USE_ACCEPTANCE, true); + getAnalyticsAcceptance = (): Promise => + LocalStorageApi.get(keys.ANALYTICS_ACCEPTANCE, false); + + setAnalyticsAcceptance = (): Promise => + LocalStorageApi.set(keys.ANALYTICS_ACCEPTANCE, true); + unsetTermsOfUseAcceptance = (): Promise => LocalStorageApi.unset(keys.TERMS_OF_USE_ACCEPTANCE); diff --git a/source/renderer/app/components/analytics/Analytics.stories.js b/source/renderer/app/components/analytics/Analytics.stories.js new file mode 100644 index 0000000000..a2aafb4702 --- /dev/null +++ b/source/renderer/app/components/analytics/Analytics.stories.js @@ -0,0 +1,68 @@ +// @flow +import React from 'react'; +import { defineMessages, IntlProvider } from 'react-intl'; +import { storiesOf } from '@storybook/react'; +import { withKnobs } from '@storybook/addon-knobs'; +import enMessages from '../../i18n/locales/en-US.json'; +import jpMessages from '../../i18n/locales/ja-JP.json'; +import AnalyticsDialog from './AnalyticsDialog'; +import { action } from '@storybook/addon-actions'; +import { observable } from 'mobx'; +import StoryDecorator from '../../../../../storybook/stories/_support/StoryDecorator'; +import StoryProvider from '../../../../../storybook/stories/_support/StoryProvider'; +import StoryLayout from '../../../../../storybook/stories/_support/StoryLayout'; + +const { intl: enIntl } = new IntlProvider({ + locale: 'en-US', + messages: enMessages, +}).getChildContext(); +const { intl: jpIntl } = new IntlProvider({ + locale: 'ja-JP', + messages: jpMessages, +}).getChildContext(); +const intl = { 'en-US': enIntl, 'ja-JP': jpIntl }; + +const messages = defineMessages({ + title: { + id: 'analytics.dialog.title', + defaultMessage: '!!!Anonymous data collection', + description: 'Analytics dialog title', + }, + description: { + id: 'analytics.dialog.description', + defaultMessage: + '!!!All data is anonymous and is used only for product development purposes. Read more in Terms and Conditions.', + description: 'Analytics data collection description', + }, + dataCollectionDetailsTitle: { + id: 'analytics.dialog.dataCollectionDetailsTitle', + defaultMessage: '!!!What data do we collect?', + description: 'Data collection details title', + }, + dataCollectionDetailsUserBehaviour: { + id: 'analytics.dialog.dataCollectionDetailsUserBehaviour', + defaultMessage: '!!!User behavior (where the user clicks)', + description: 'Description for the user behaviour data collection', + }, + dataCollectionDetailsDeviceInfo: { + id: 'analytics.dialog.dataCollectionDetailsDeviceInfo', + defaultMessage: '!!!Device info (OS, RAM, disk space, etc)', + description: 'Description for the device info data collection', + }, + dataCollectionSwitchButton: { + id: 'analytics.dialog.dataCollectionSwitchText', + defaultMessage: '!!!Allow anonymous data collection', + description: 'Data collection agreement switch button label', + }, + confirmButton: { + id: 'analytics.dialog.confirmButton', + defaultMessage: '!!!Confirm', + description: 'Analytics data collection confirmation button text', + }, +}); + +storiesOf('Analytics', module) + .addDecorator((story) => {story()}) + .addDecorator(withKnobs) + + .add('Analytics Dialog', () => ); diff --git a/source/renderer/app/components/analytics/AnalyticsDIalog.scss b/source/renderer/app/components/analytics/AnalyticsDIalog.scss new file mode 100644 index 0000000000..b21b3c18c8 --- /dev/null +++ b/source/renderer/app/components/analytics/AnalyticsDIalog.scss @@ -0,0 +1,62 @@ +.component { + align-self: center; + color: var(--theme-main-body-messages-color); + display: flex; + flex: 1; + flex-direction: column; + font-family: var(--font-regular); + justify-self: center; + line-height: 1.38; +} + +// todo is this is needed? For now just copy/paste for consistency +.component::before, +.component::after { + content: ''; + display: block; + flex: 1; + min-height: 20px; +} + +.centeredBox { + background-color: var(--theme-bordered-box-background-color); + border: var(--theme-bordered-box-border); + border-radius: 10px; + padding: 30px 30px 20px; + width: 620px; +} + +.submitButton { + display: block !important; + margin: 0 auto; +} + +// TODO consider improving react-polymorph inputs to handle `labelPlacement` property e.g. left | right +.switch { + flex-direction: row-reverse; + margin: 0 0 30px; +} + +.title, +.dataCollectionTitle { + font-weight: bold; + margin-bottom: 14px; +} + +.description { + margin-bottom: 20px; +} + +.descriptionLink { + color: var(--theme-staking-stake-pool-tooltip-link-color); +} + +.dataCollectionList { + list-style: auto; + list-style-position: inside; + padding-left: 14px; +} + +.hr { + margin: 20px 0; +} diff --git a/source/renderer/app/components/analytics/AnalyticsDialog.js b/source/renderer/app/components/analytics/AnalyticsDialog.js new file mode 100644 index 0000000000..7db20e8be3 --- /dev/null +++ b/source/renderer/app/components/analytics/AnalyticsDialog.js @@ -0,0 +1,121 @@ +// @flow +import React, { Component, useCallback, useState } from 'react'; +import ReactModal from 'react-modal'; +import { observer } from 'mobx-react'; +import SVGInline from 'react-svg-inline'; +import classnames from 'classnames'; +import { get } from 'lodash'; +import { + defineMessages, + FormattedHTMLMessage, + intlShape, + injectIntl, +} from 'react-intl'; +import { Button } from 'react-polymorph/lib/components/Button'; +import { Checkbox } from 'react-polymorph/lib/components/Checkbox'; +import { SwitchSkin } from 'react-polymorph/lib/skins/simple/SwitchSkin'; +import { Link } from 'react-polymorph/lib/components/Link'; +import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; +import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; +import { ButtonSpinnerSkin } from 'react-polymorph/lib/skins/simple/ButtonSpinnerSkin'; +import ReactMarkdown from 'react-markdown'; +import styles from './AnalyticsDIalog.scss'; +import DialogCloseButton from '../widgets/DialogCloseButton'; +import ProgressBarLarge from '../widgets/ProgressBarLarge'; +import externalLinkIcon from '../../assets/images/link-ic.inline.svg'; +import closeCrossThin from '../../assets/images/close-cross-thin.inline.svg'; +import type { InjectedProps } from '../../types/injectedPropsType'; +import TopBar from '../layout/TopBar'; +import About from '../static/About'; +import classNames from 'classnames'; +import NormalSwitch from '../widgets/forms/NormalSwitch'; +import { Intl } from '../../types/i18nTypes'; + +const messages = defineMessages({ + title: { + id: 'analytics.dialog.title', + defaultMessage: '!!!Anonymous data collection', + description: 'Analytics dialog title', + }, + description: { + id: 'analytics.dialog.description', + defaultMessage: + '!!!All data is anonymous and is used only for product development purposes. Read more in Terms and Conditions.', + description: 'Analytics data collection description', + }, + dataCollectionDetailsTitle: { + id: 'analytics.dialog.dataCollectionDetailsTitle', + defaultMessage: '!!!What data do we collect?', + description: 'Data collection details title', + }, + dataCollectionDetailsUserBehaviour: { + id: 'analytics.dialog.dataCollectionDetailsUserBehaviour', + defaultMessage: '!!!User behavior (where the user clicks)', + description: 'Description for the user behaviour data collection', + }, + dataCollectionDetailsDeviceInfo: { + id: 'analytics.dialog.dataCollectionDetailsDeviceInfo', + defaultMessage: '!!!Device info (OS, RAM, disk space, etc)', + description: 'Description for the device info data collection', + }, + dataCollectionSwitchButton: { + id: 'analytics.dialog.dataCollectionSwitchText', + defaultMessage: '!!!Allow anonymous data collection', + description: 'Data collection agreement switch button label', + }, + confirmButton: { + id: 'analytics.dialog.confirmButton', + defaultMessage: '!!!Confirm', + description: 'Analytics data collection confirmation button text', + }, +}); + +type Props = { + intl: Intl, +}; + +const AnalyticsDialog = ({ intl }: Props) => { + const [open, setOpen] = useState(true); + const toggleOpen = useCallback(() => { + setOpen((prevOpen) => !prevOpen); + }, [setOpen]); + + return ( +
+
+

{intl.formatMessage(messages.title)}

+

+ {intl.formatMessage(messages.description)} +

+ {/* TODO create a common accordion/expandable component, based on DappTransactionRequest.js - metadata toggle */} +

+ {intl.formatMessage(messages.dataCollectionDetailsTitle)} +

+
    +
  1. + {intl.formatMessage(messages.dataCollectionDetailsUserBehaviour)} +
  2. +
  3. + {intl.formatMessage(messages.dataCollectionDetailsDeviceInfo)} +
  4. +
+
+ +
+
+ ); +}; + +export default injectIntl(AnalyticsDialog); diff --git a/source/renderer/app/components/widgets/forms/NormalSwitch.js b/source/renderer/app/components/widgets/forms/NormalSwitch.js index 0b202ea074..01e6bb1df2 100644 --- a/source/renderer/app/components/widgets/forms/NormalSwitch.js +++ b/source/renderer/app/components/widgets/forms/NormalSwitch.js @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import { Checkbox } from 'react-polymorph/lib/components/Checkbox'; import { SwitchSkin } from 'react-polymorph/lib/skins/simple/SwitchSkin'; import { IDENTIFIERS } from 'react-polymorph/lib/themes/API'; +import classnames from 'classnames'; import styles from './NormalSwitch.scss'; type Props = { @@ -15,7 +16,7 @@ export default class NormalSwitch extends Component { render() { return ( { + static defaultProps = { actions: null, stores: null }; + + onSubmit = () => { + this.props.actions.profile.acceptTermsOfUse.trigger(); + }; + + render() { + // const { app, networkStatus, profile } = this.props.stores; + // const { setTermsOfUseAcceptanceRequest, termsOfUse } = profile; + const { currentRoute, openExternalLink } = app; + const { isShelleyActivated } = networkStatus; + // const isSubmitting = setTermsOfUseAcceptanceRequest.isExecuting; + const topbar = ( + + ); + + return ( + + {/* */} + + + ); + } +} diff --git a/source/renderer/app/routes-config.js b/source/renderer/app/routes-config.js index 5665c52be3..ad87fd2180 100644 --- a/source/renderer/app/routes-config.js +++ b/source/renderer/app/routes-config.js @@ -17,6 +17,7 @@ export const ROUTES = { PROFILE: { INITIAL_SETTINGS: '/profile/initial-settings', TERMS_OF_USE: '/profile/terms-of-service', + ANALYTICS: '/profile/analytics', DATA_LAYER_MIGRATION: '/profile/data-layer-migration', }, WALLETS: { diff --git a/source/renderer/app/stores/AppStore.js b/source/renderer/app/stores/AppStore.js index dc9743f814..3452b6f19f 100644 --- a/source/renderer/app/stores/AppStore.js +++ b/source/renderer/app/stores/AppStore.js @@ -130,7 +130,8 @@ export default class AppStore extends Store { @computed get isSetupPage(): boolean { return ( this.currentRoute === ROUTES.PROFILE.INITIAL_SETTINGS || - this.currentRoute === ROUTES.PROFILE.TERMS_OF_USE + this.currentRoute === ROUTES.PROFILE.TERMS_OF_USE || + this.currentRoute === ROUTES.PROFILE.ANALYTICS ); } diff --git a/source/renderer/app/stores/ProfileStore.js b/source/renderer/app/stores/ProfileStore.js index 78a59a7368..98558faa18 100644 --- a/source/renderer/app/stores/ProfileStore.js +++ b/source/renderer/app/stores/ProfileStore.js @@ -89,6 +89,12 @@ export default class ProfileStore extends Store { @observable setTermsOfUseAcceptanceRequest: Request = new Request( this.api.localStorage.setTermsOfUseAcceptance ); + @observable getAnalyticsAcceptanceRequest: Request = new Request( + this.api.localStorage.getAnalyticsAcceptance + ); + @observable setAnalyticsAcceptanceRequest: Request = new Request( + this.api.localStorage.setAnalyticsAcceptance + ); @observable getDataLayerMigrationAcceptanceRequest: Request = new Request( this.api.localStorage.getDataLayerMigrationAcceptance @@ -118,6 +124,7 @@ export default class ProfileStore extends Store { this._finishInitialScreenSettings ); profileActions.updateUserLocalSetting.listen(this._updateUserLocalSetting); + profileActions.acceptAnalytics.listen(this._acceptAnalytics); profileActions.acceptTermsOfUse.listen(this._acceptTermsOfUse); profileActions.acceptDataLayerMigration.listen( this._acceptDataLayerMigration @@ -132,12 +139,15 @@ export default class ProfileStore extends Store { this.registerReactions([ this._updateBigNumberFormat, this._redirectToInitialSettingsIfNoLocaleSet, + // todo check for correct order + this._redirectToAnalyticsScreenIfNotAccepted, this._redirectToTermsOfUseScreenIfTermsNotAccepted, // this._redirectToDataLayerMigrationScreenIfMigrationHasNotAccepted, this._redirectToMainUiAfterTermsAreAccepted, this._redirectToMainUiAfterDataLayerMigrationIsAccepted, ]); this._getTermsOfUseAcceptance(); + this._getAnalyticsAcceptance(); this._getDataLayerMigrationAcceptance(); this._getDesktopDirectoryPath(); this._getSystemLocale(); @@ -237,6 +247,17 @@ export default class ProfileStore extends Store { return this.getTermsOfUseAcceptanceRequest.result === true; } + @computed get hasLoadedAnalyticsAcceptance(): boolean { + return ( + this.getAnalyticsAcceptanceRequest.wasExecuted && + this.getAnalyticsAcceptanceRequest.result !== null + ); + } + + @computed get areAnalyticsAccepted(): boolean { + return this.getAnalyticsAcceptanceRequest.result === true; + } + @computed get hasLoadedDataLayerMigrationAcceptance(): boolean { return ( this.getDataLayerMigrationAcceptanceRequest.wasExecuted && @@ -306,6 +327,19 @@ export default class ProfileStore extends Store { } }; + _acceptAnalytics = async () => { + await this.setAnalyticsAcceptanceRequest.execute(); + await this.getAnalyticsAcceptanceRequest.execute(); + await enableApplicationMenuNavigationChannel.send(); + }; + + _getAnalyticsAcceptance = async () => { + await this.getAnalyticsAcceptanceRequest.execute(); + if (this.getAnalyticsAcceptanceRequest.result) { + await enableApplicationMenuNavigationChannel.send(); + } + }; + _acceptDataLayerMigration = async () => { await this.setDataLayerMigrationAcceptanceRequest.execute(); await this.getDataLayerMigrationAcceptanceRequest.execute(); @@ -356,6 +390,21 @@ export default class ProfileStore extends Store { _isOnTermsOfUsePage = () => this.stores.app.currentRoute === ROUTES.PROFILE.TERMS_OF_USE; + _redirectToAnalyticsScreenIfNotAccepted = () => { + const analyticsNotAccepted = + this.hasLoadedAnalyticsAcceptance && !this.areAnalyticsAccepted; + if ( + !this.isInitialScreen && + this.isCurrentLocaleSet && + this.areAnalyticsAccepted && + analyticsNotAccepted + ) { + this.actions.router.goToRoute.trigger({ + route: ROUTES.PROFILE.ANALYTICS, + }); + } + }; + _redirectToDataLayerMigrationScreenIfMigrationHasNotAccepted = () => { const { isConnected } = this.stores.networkStatus; const dataLayerMigrationNotAccepted = @@ -365,6 +414,7 @@ export default class ProfileStore extends Store { isConnected && this.isCurrentLocaleSet && this.areTermsOfUseAccepted && + this.areAnalyticsAccepted && this.stores.wallets.hasLoadedWallets && dataLayerMigrationNotAccepted ) { diff --git a/storybook/stories/index.js b/storybook/stories/index.js index 474e21ae00..0922e24477 100644 --- a/storybook/stories/index.js +++ b/storybook/stories/index.js @@ -46,3 +46,7 @@ import './common/ItemsDropdown.stories'; // Discreet Mode import '../../source/renderer/app/features/discreet-mode/ui/discreet-toggle/DiscreetModeToggle.story'; import '../../source/renderer/app/features/discreet-mode/ui/DiscreetValue.story'; + +// Analytics + +import '../../source/renderer/app/components/analytics/Analytics.stories'; From 3ee1643537a470dae19a7989097aa49a67592d91 Mon Sep 17 00:00:00 2001 From: "przemyslaw.wlodek" Date: Wed, 5 Jan 2022 00:58:27 +0100 Subject: [PATCH 02/76] [DDW-809] Clean-up and updated design + translations --- .../components/analytics/Analytics.stories.js | 62 +---------- .../components/analytics/AnalyticsDialog.js | 98 +++++++++-------- ...lyticsDIalog.scss => AnalyticsDialog.scss} | 44 +++++++- .../app/components/widgets/Accordion.js | 20 ++++ .../app/i18n/locales/defaultMessages.json | 103 ++++++++++++++++++ source/renderer/app/i18n/locales/en-US.json | 9 +- source/renderer/app/i18n/locales/ja-JP.json | 7 ++ 7 files changed, 235 insertions(+), 108 deletions(-) rename source/renderer/app/components/analytics/{AnalyticsDIalog.scss => AnalyticsDialog.scss} (51%) create mode 100644 source/renderer/app/components/widgets/Accordion.js diff --git a/source/renderer/app/components/analytics/Analytics.stories.js b/source/renderer/app/components/analytics/Analytics.stories.js index a2aafb4702..4c68cf7437 100644 --- a/source/renderer/app/components/analytics/Analytics.stories.js +++ b/source/renderer/app/components/analytics/Analytics.stories.js @@ -1,68 +1,16 @@ // @flow import React from 'react'; -import { defineMessages, IntlProvider } from 'react-intl'; import { storiesOf } from '@storybook/react'; import { withKnobs } from '@storybook/addon-knobs'; -import enMessages from '../../i18n/locales/en-US.json'; -import jpMessages from '../../i18n/locales/ja-JP.json'; import AnalyticsDialog from './AnalyticsDialog'; -import { action } from '@storybook/addon-actions'; -import { observable } from 'mobx'; import StoryDecorator from '../../../../../storybook/stories/_support/StoryDecorator'; -import StoryProvider from '../../../../../storybook/stories/_support/StoryProvider'; -import StoryLayout from '../../../../../storybook/stories/_support/StoryLayout'; - -const { intl: enIntl } = new IntlProvider({ - locale: 'en-US', - messages: enMessages, -}).getChildContext(); -const { intl: jpIntl } = new IntlProvider({ - locale: 'ja-JP', - messages: jpMessages, -}).getChildContext(); -const intl = { 'en-US': enIntl, 'ja-JP': jpIntl }; - -const messages = defineMessages({ - title: { - id: 'analytics.dialog.title', - defaultMessage: '!!!Anonymous data collection', - description: 'Analytics dialog title', - }, - description: { - id: 'analytics.dialog.description', - defaultMessage: - '!!!All data is anonymous and is used only for product development purposes. Read more in Terms and Conditions.', - description: 'Analytics data collection description', - }, - dataCollectionDetailsTitle: { - id: 'analytics.dialog.dataCollectionDetailsTitle', - defaultMessage: '!!!What data do we collect?', - description: 'Data collection details title', - }, - dataCollectionDetailsUserBehaviour: { - id: 'analytics.dialog.dataCollectionDetailsUserBehaviour', - defaultMessage: '!!!User behavior (where the user clicks)', - description: 'Description for the user behaviour data collection', - }, - dataCollectionDetailsDeviceInfo: { - id: 'analytics.dialog.dataCollectionDetailsDeviceInfo', - defaultMessage: '!!!Device info (OS, RAM, disk space, etc)', - description: 'Description for the device info data collection', - }, - dataCollectionSwitchButton: { - id: 'analytics.dialog.dataCollectionSwitchText', - defaultMessage: '!!!Allow anonymous data collection', - description: 'Data collection agreement switch button label', - }, - confirmButton: { - id: 'analytics.dialog.confirmButton', - defaultMessage: '!!!Confirm', - description: 'Analytics data collection confirmation button text', - }, -}); storiesOf('Analytics', module) .addDecorator((story) => {story()}) .addDecorator(withKnobs) - .add('Analytics Dialog', () => ); + .add('Analytics Dialog', () => ( +
+ +
+ )); diff --git a/source/renderer/app/components/analytics/AnalyticsDialog.js b/source/renderer/app/components/analytics/AnalyticsDialog.js index 7db20e8be3..16fa181d89 100644 --- a/source/renderer/app/components/analytics/AnalyticsDialog.js +++ b/source/renderer/app/components/analytics/AnalyticsDialog.js @@ -1,35 +1,12 @@ // @flow -import React, { Component, useCallback, useState } from 'react'; -import ReactModal from 'react-modal'; -import { observer } from 'mobx-react'; -import SVGInline from 'react-svg-inline'; -import classnames from 'classnames'; -import { get } from 'lodash'; -import { - defineMessages, - FormattedHTMLMessage, - intlShape, - injectIntl, -} from 'react-intl'; +import React, { useCallback, useState } from 'react'; +import { defineMessages, injectIntl } from 'react-intl'; import { Button } from 'react-polymorph/lib/components/Button'; -import { Checkbox } from 'react-polymorph/lib/components/Checkbox'; -import { SwitchSkin } from 'react-polymorph/lib/skins/simple/SwitchSkin'; -import { Link } from 'react-polymorph/lib/components/Link'; -import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; -import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; import { ButtonSpinnerSkin } from 'react-polymorph/lib/skins/simple/ButtonSpinnerSkin'; -import ReactMarkdown from 'react-markdown'; -import styles from './AnalyticsDIalog.scss'; -import DialogCloseButton from '../widgets/DialogCloseButton'; -import ProgressBarLarge from '../widgets/ProgressBarLarge'; -import externalLinkIcon from '../../assets/images/link-ic.inline.svg'; -import closeCrossThin from '../../assets/images/close-cross-thin.inline.svg'; -import type { InjectedProps } from '../../types/injectedPropsType'; -import TopBar from '../layout/TopBar'; -import About from '../static/About'; -import classNames from 'classnames'; +import styles from './AnalyticsDialog.scss'; import NormalSwitch from '../widgets/forms/NormalSwitch'; import { Intl } from '../../types/i18nTypes'; +import globalMessages from '../../i18n/global-messages'; const messages = defineMessages({ title: { @@ -72,13 +49,31 @@ const messages = defineMessages({ type Props = { intl: Intl, + loading: boolean, + onConfirm: Function, }; -const AnalyticsDialog = ({ intl }: Props) => { - const [open, setOpen] = useState(true); - const toggleOpen = useCallback(() => { - setOpen((prevOpen) => !prevOpen); - }, [setOpen]); +const AnalyticsDialog = ({ intl, loading, onConfirm }: Props) => { + const [showDataCollectionDetails, setShowDataCollectionDetails] = useState( + false + ); + const toggleShowDataCollectionDetails = useCallback(() => { + setShowDataCollectionDetails( + (prevAllowDataCollection) => !prevAllowDataCollection + ); + }, [setShowDataCollectionDetails]); + const [allowDataCollection, setAllowDataCollection] = useState(true); + const toggleAllowDataCollection = useCallback(() => { + setAllowDataCollection( + (prevAllowDataCollection) => !prevAllowDataCollection + ); + }, [setAllowDataCollection]); + const getShowDataCollectionDetailsToggleLabel = useCallback( + (isVisible: boolean) => + isVisible + ? intl.formatMessage(globalMessages.hide) + : intl.formatMessage(globalMessages.view) + ); return (
@@ -87,22 +82,33 @@ const AnalyticsDialog = ({ intl }: Props) => {

{intl.formatMessage(messages.description)}

- {/* TODO create a common accordion/expandable component, based on DappTransactionRequest.js - metadata toggle */}

{intl.formatMessage(messages.dataCollectionDetailsTitle)} +

-
    -
  1. - {intl.formatMessage(messages.dataCollectionDetailsUserBehaviour)} -
  2. -
  3. - {intl.formatMessage(messages.dataCollectionDetailsDeviceInfo)} -
  4. -
-
+ {showDataCollectionDetails && ( + <> +
    +
  1. + {intl.formatMessage( + messages.dataCollectionDetailsUserBehaviour + )} +
  2. +
  3. + {intl.formatMessage(messages.dataCollectionDetailsDeviceInfo)} +
  4. +
+
+ + )} @@ -110,8 +116,8 @@ const AnalyticsDialog = ({ intl }: Props) => { className={styles.submitButton} label={intl.formatMessage(messages.confirmButton)} skin={ButtonSpinnerSkin} - loading={false} - onClick={() => {}} + loading={loading} + onClick={onConfirm} />
diff --git a/source/renderer/app/components/analytics/AnalyticsDIalog.scss b/source/renderer/app/components/analytics/AnalyticsDialog.scss similarity index 51% rename from source/renderer/app/components/analytics/AnalyticsDIalog.scss rename to source/renderer/app/components/analytics/AnalyticsDialog.scss index b21b3c18c8..41b84b01fe 100644 --- a/source/renderer/app/components/analytics/AnalyticsDIalog.scss +++ b/source/renderer/app/components/analytics/AnalyticsDialog.scss @@ -31,10 +31,14 @@ margin: 0 auto; } -// TODO consider improving react-polymorph inputs to handle `labelPlacement` property e.g. left | right +// TODO improve react-polymorph inputs to handle `labelPlacement` property e.g. left | right .switch { flex-direction: row-reverse; margin: 0 0 30px; + + & label { + margin: 0 !important; + } } .title, @@ -43,6 +47,11 @@ margin-bottom: 14px; } +.dataCollectionTitle { + align-items: center; + display: flex; +} + .description { margin-bottom: 20px; } @@ -52,11 +61,38 @@ } .dataCollectionList { - list-style: auto; - list-style-position: inside; - padding-left: 14px; + line-height: 2; // todo discuss with Sasha + list-style: auto outside; + padding-left: 30px; // todo discuss with Sasha +} + +.dataCollectionListItem { + padding-left: 10px; // todo discuss with Sasha +} + +.dataCollectionListItem::marker { + font-weight: bold; } .hr { + border: 0; + border-top: 1px solid var(--theme-dapp-transaction-request-separator); margin: 20px 0; + opacity: 0.3; +} + +.toggleButton { + background-color: var( + --theme-dapp-transaction-request-toggle-button-background-color + ); + border-radius: 4px; + color: var(--theme-label-button-color); + cursor: pointer; + font-family: var(--font-medium); + font-size: 10px; + letter-spacing: 0.5px; + line-height: 1.2; + margin-left: 10px; + padding: 4px 8px; + text-transform: uppercase; } diff --git a/source/renderer/app/components/widgets/Accordion.js b/source/renderer/app/components/widgets/Accordion.js new file mode 100644 index 0000000000..1d3881ce8c --- /dev/null +++ b/source/renderer/app/components/widgets/Accordion.js @@ -0,0 +1,20 @@ +// @flow +import React, { Component } from 'react'; +import SVGInline from 'react-svg-inline'; +import backArrow from '../../assets/images/back-arrow-ic.inline.svg'; +import styles from './DialogBackButton.scss'; + +type Props = { + onBack: Function, +}; + +export default class DialogBackButton extends Component { + render() { + const { onBack } = this.props; + return ( + + ); + } +} diff --git a/source/renderer/app/i18n/locales/defaultMessages.json b/source/renderer/app/i18n/locales/defaultMessages.json index 078fce7556..0fd6856a94 100644 --- a/source/renderer/app/i18n/locales/defaultMessages.json +++ b/source/renderer/app/i18n/locales/defaultMessages.json @@ -289,6 +289,109 @@ ], "path": "source/renderer/app/api/errors.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!Anonymous data collection", + "description": "Analytics dialog title", + "end": { + "column": 3, + "line": 39 + }, + "file": "source/renderer/app/components/analytics/AnalyticsDialog.js", + "id": "analytics.dialog.title", + "start": { + "column": 9, + "line": 35 + } + }, + { + "defaultMessage": "!!!All data is anonymous and is used only for product development purposes. Read more in Terms and Conditions.", + "description": "Analytics data collection description", + "end": { + "column": 3, + "line": 45 + }, + "file": "source/renderer/app/components/analytics/AnalyticsDialog.js", + "id": "analytics.dialog.description", + "start": { + "column": 15, + "line": 40 + } + }, + { + "defaultMessage": "!!!What data do we collect?", + "description": "Data collection details title", + "end": { + "column": 3, + "line": 50 + }, + "file": "source/renderer/app/components/analytics/AnalyticsDialog.js", + "id": "analytics.dialog.dataCollectionDetailsTitle", + "start": { + "column": 30, + "line": 46 + } + }, + { + "defaultMessage": "!!!User behavior (where the user clicks)", + "description": "Description for the user behaviour data collection", + "end": { + "column": 3, + "line": 55 + }, + "file": "source/renderer/app/components/analytics/AnalyticsDialog.js", + "id": "analytics.dialog.dataCollectionDetailsUserBehaviour", + "start": { + "column": 38, + "line": 51 + } + }, + { + "defaultMessage": "!!!Device info (OS, RAM, disk space, etc)", + "description": "Description for the device info data collection", + "end": { + "column": 3, + "line": 60 + }, + "file": "source/renderer/app/components/analytics/AnalyticsDialog.js", + "id": "analytics.dialog.dataCollectionDetailsDeviceInfo", + "start": { + "column": 35, + "line": 56 + } + }, + { + "defaultMessage": "!!!Allow anonymous data collection", + "description": "Data collection agreement switch button label", + "end": { + "column": 3, + "line": 65 + }, + "file": "source/renderer/app/components/analytics/AnalyticsDialog.js", + "id": "analytics.dialog.dataCollectionSwitchText", + "start": { + "column": 30, + "line": 61 + } + }, + { + "defaultMessage": "!!!Confirm", + "description": "Analytics data collection confirmation button text", + "end": { + "column": 3, + "line": 70 + }, + "file": "source/renderer/app/components/analytics/AnalyticsDialog.js", + "id": "analytics.dialog.confirmButton", + "start": { + "column": 17, + "line": 66 + } + } + ], + "path": "source/renderer/app/components/analytics/AnalyticsDialog.json" + }, { "descriptors": [ { diff --git a/source/renderer/app/i18n/locales/en-US.json b/source/renderer/app/i18n/locales/en-US.json index c193bf71ba..a0204e3ece 100755 --- a/source/renderer/app/i18n/locales/en-US.json +++ b/source/renderer/app/i18n/locales/en-US.json @@ -1,4 +1,11 @@ { + "analytics.dialog.confirmButton": "Confirm", + "analytics.dialog.dataCollectionDetailsDeviceInfo": "Device info (OS, RAM, disk space, etc)", + "analytics.dialog.dataCollectionDetailsTitle": "What data do we collect?", + "analytics.dialog.dataCollectionDetailsUserBehaviour": "User behavior (where the user clicks)", + "analytics.dialog.dataCollectionSwitchText": "Allow anonymous data collection", + "analytics.dialog.description": "All data is anonymous and is used only for product development purposes. Read more in Terms and Conditions.", + "analytics.dialog.title": "Anonymous data collection", "api.errors.ApiMethodNotYetImplementedError": "This API method is not yet implemented.", "api.errors.CanNotCalculateTransactionFeesError": "Cannot calculate fees while there are pending transactions.", "api.errors.ForbiddenMnemonicError": "Invalid recovery phrase. Submitted recovery phrase is one of the example recovery phrases from the documentation and should not be used for wallets holding funds.", @@ -1248,4 +1255,4 @@ "wallet.transferFunds.dialog2.total.label": "Total", "widgets.itemsDropdown.syncingLabel": "Syncing", "widgets.itemsDropdown.syncingLabelProgress": "Syncing {syncingProgress}%" -} \ No newline at end of file +} diff --git a/source/renderer/app/i18n/locales/ja-JP.json b/source/renderer/app/i18n/locales/ja-JP.json index 7b3b031874..b86f709c05 100755 --- a/source/renderer/app/i18n/locales/ja-JP.json +++ b/source/renderer/app/i18n/locales/ja-JP.json @@ -1,4 +1,11 @@ { + "analytics.dialog.confirmButton": "!!!Confirm", + "analytics.dialog.dataCollectionDetailsDeviceInfo": "!!!Device info (OS, RAM, disk space, etc)", + "analytics.dialog.dataCollectionDetailsTitle": "!!!What data do we collect?", + "analytics.dialog.dataCollectionDetailsUserBehaviour": "!!!User behavior (where the user clicks)", + "analytics.dialog.dataCollectionSwitchText": "!!!Allow anonymous data collection", + "analytics.dialog.description": "!!!All data is anonymous and is used only for product development purposes. Read more in Terms and Conditions.", + "analytics.dialog.title": "!!!Anonymous data collection", "api.errors.ApiMethodNotYetImplementedError": "このAPIはまだ実装されていません。", "api.errors.CanNotCalculateTransactionFeesError": "処理中のトランザクションがあるため、手数料を計算できません。", "api.errors.ForbiddenMnemonicError": "ウォレットの復元フレーズが無効です。送信された復元フレーズは参照用復元フレーズ例の1つです。資金を保有しているウォレットには使用できません。", From 20e7706e22d5e37aa16ae94a9a22c3ef5c787568 Mon Sep 17 00:00:00 2001 From: "przemyslaw.wlodek" Date: Wed, 5 Jan 2022 18:13:11 +0100 Subject: [PATCH 03/76] [DDW-809] Clean-up and updated initial settings flow --- source/renderer/app/App.js | 2 -- source/renderer/app/api/utils/localStorage.js | 7 +++-- .../analytics/Analytics.stories.js | 2 +- .../analytics/AnalyticsDialog.js | 13 +++++----- .../analytics/AnalyticsDialog.scss | 26 ++++++------------- .../app/containers/profile/AnalyticsPage.js | 25 +++++++----------- source/renderer/app/stores/ProfileStore.js | 4 +-- storybook/stories/index.js | 2 +- 8 files changed, 34 insertions(+), 47 deletions(-) rename source/renderer/app/components/{ => profile}/analytics/Analytics.stories.js (82%) rename source/renderer/app/components/{ => profile}/analytics/AnalyticsDialog.js (89%) rename source/renderer/app/components/{ => profile}/analytics/AnalyticsDialog.scss (82%) diff --git a/source/renderer/app/App.js b/source/renderer/app/App.js index 68e5853036..211f523247 100755 --- a/source/renderer/app/App.js +++ b/source/renderer/app/App.js @@ -20,7 +20,6 @@ import { DIALOGS } from '../../common/ipc/constants'; import type { StoresMap } from './stores/index'; import type { ActionsMap } from './actions/index'; import NewsFeedContainer from './containers/news/NewsFeedContainer'; -import AnalyticsDialog from './components/analytics/AnalyticsDialog'; @observer export default class App extends Component<{ @@ -71,7 +70,6 @@ export default class App extends Component<{ {mobxDevTools} - {/* */} {[ isActiveDialog(ABOUT) && , isActiveDialog(DAEDALUS_DIAGNOSTICS) && ( diff --git a/source/renderer/app/api/utils/localStorage.js b/source/renderer/app/api/utils/localStorage.js index fe0f361d72..76b791403f 100644 --- a/source/renderer/app/api/utils/localStorage.js +++ b/source/renderer/app/api/utils/localStorage.js @@ -174,14 +174,17 @@ export default class LocalStorageApi { setTermsOfUseAcceptance = (): Promise => LocalStorageApi.set(keys.TERMS_OF_USE_ACCEPTANCE, true); + unsetTermsOfUseAcceptance = (): Promise => + LocalStorageApi.unset(keys.TERMS_OF_USE_ACCEPTANCE); + getAnalyticsAcceptance = (): Promise => LocalStorageApi.get(keys.ANALYTICS_ACCEPTANCE, false); setAnalyticsAcceptance = (): Promise => LocalStorageApi.set(keys.ANALYTICS_ACCEPTANCE, true); - unsetTermsOfUseAcceptance = (): Promise => - LocalStorageApi.unset(keys.TERMS_OF_USE_ACCEPTANCE); + unsetAnalyticsAcceptance = (): Promise => + LocalStorageApi.set(keys.ANALYTICS_ACCEPTANCE, false); getUserTheme = (): Promise => LocalStorageApi.get(keys.THEME); diff --git a/source/renderer/app/components/analytics/Analytics.stories.js b/source/renderer/app/components/profile/analytics/Analytics.stories.js similarity index 82% rename from source/renderer/app/components/analytics/Analytics.stories.js rename to source/renderer/app/components/profile/analytics/Analytics.stories.js index 4c68cf7437..ea81078677 100644 --- a/source/renderer/app/components/analytics/Analytics.stories.js +++ b/source/renderer/app/components/profile/analytics/Analytics.stories.js @@ -3,7 +3,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { withKnobs } from '@storybook/addon-knobs'; import AnalyticsDialog from './AnalyticsDialog'; -import StoryDecorator from '../../../../../storybook/stories/_support/StoryDecorator'; +import StoryDecorator from '../../../../../../storybook/stories/_support/StoryDecorator'; storiesOf('Analytics', module) .addDecorator((story) => {story()}) diff --git a/source/renderer/app/components/analytics/AnalyticsDialog.js b/source/renderer/app/components/profile/analytics/AnalyticsDialog.js similarity index 89% rename from source/renderer/app/components/analytics/AnalyticsDialog.js rename to source/renderer/app/components/profile/analytics/AnalyticsDialog.js index 16fa181d89..351ba346e0 100644 --- a/source/renderer/app/components/analytics/AnalyticsDialog.js +++ b/source/renderer/app/components/profile/analytics/AnalyticsDialog.js @@ -4,9 +4,9 @@ import { defineMessages, injectIntl } from 'react-intl'; import { Button } from 'react-polymorph/lib/components/Button'; import { ButtonSpinnerSkin } from 'react-polymorph/lib/skins/simple/ButtonSpinnerSkin'; import styles from './AnalyticsDialog.scss'; -import NormalSwitch from '../widgets/forms/NormalSwitch'; -import { Intl } from '../../types/i18nTypes'; -import globalMessages from '../../i18n/global-messages'; +import NormalSwitch from '../../widgets/forms/NormalSwitch'; +import { Intl } from '../../../types/i18nTypes'; +import globalMessages from '../../../i18n/global-messages'; const messages = defineMessages({ title: { @@ -17,7 +17,8 @@ const messages = defineMessages({ description: { id: 'analytics.dialog.description', defaultMessage: - '!!!All data is anonymous and is used only for product development purposes. Read more in Terms and Conditions.', + // todo discuss terms and conditions link interpolation + '!!!All data is anonymous and is only used for product development purposes. Read more in the Terms and Conditions.', description: 'Analytics data collection description', }, dataCollectionDetailsTitle: { @@ -32,7 +33,7 @@ const messages = defineMessages({ }, dataCollectionDetailsDeviceInfo: { id: 'analytics.dialog.dataCollectionDetailsDeviceInfo', - defaultMessage: '!!!Device info (OS, RAM, disk space, etc)', + defaultMessage: '!!!Device information (OS, RAM, disk space, etc)', description: 'Description for the device info data collection', }, dataCollectionSwitchButton: { @@ -110,7 +111,7 @@ const AnalyticsDialog = ({ intl, loading, onConfirm }: Props) => { onChange={toggleAllowDataCollection} checked={allowDataCollection} label={intl.formatMessage(messages.dataCollectionSwitchButton)} - className={styles.switch} + className={styles.switchButton} />