diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e09980c20..3368762101 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ Changelog ## vNext +### Features + +- Implemented date, time and number format user options ([PR 1611](https://github.com/input-output-hk/daedalus/pull/1611)) + ### Chores - Fixed build mode of webpack auto dll plugin ([PR 1606](https://github.com/input-output-hk/daedalus/pull/1606)) diff --git a/source/common/types/number.types.js b/source/common/types/number.types.js new file mode 100644 index 0000000000..07b7dba79b --- /dev/null +++ b/source/common/types/number.types.js @@ -0,0 +1,33 @@ +// @flow +export type NumberFormat = { + groupSeparator: '.' | ',' | ' ', + decimalSeparator: '.' | ',' | ' ', +}; + +type NumbersFormat = { + [key: string]: NumberFormat, +}; + +export const NUMBER_FORMATS: NumbersFormat = { + 'number-1': { + groupSeparator: ',', + decimalSeparator: '.', + }, + 'number-2': { + groupSeparator: '.', + decimalSeparator: ',', + }, + 'number-3': { + groupSeparator: ' ', + decimalSeparator: '.', + }, +}; + +export const DEFAULT_NUMBER_FORMAT: Object = { + decimalSeparator: '.', + groupSeparator: ',', + groupSize: 3, + secondaryGroupSize: 0, + fractionGroupSeparator: ' ', + fractionGroupSize: 0, +}; diff --git a/source/renderer/app/Routes.js b/source/renderer/app/Routes.js index 0880aed7d4..264da22442 100644 --- a/source/renderer/app/Routes.js +++ b/source/renderer/app/Routes.js @@ -5,7 +5,7 @@ import { ROUTES } from './routes-config'; // PAGES import Root from './containers/Root'; -import LanguageSelectionPage from './containers/profile/LanguageSelectionPage'; +import InitialSettingsPage from './containers/profile/InitialSettingsPage'; import Settings from './containers/settings/Settings'; import GeneralSettingsPage from './containers/settings/categories/GeneralSettingsPage'; import SupportSettingsPage from './containers/settings/categories/SupportSettingsPage'; @@ -34,8 +34,8 @@ export const Routes = ( = new Action(); downloadLogsSuccess: Action = new Action(); - updateLocale: Action<{ locale: string }> = new Action(); + updateUserLocalSetting: Action<{ + param: string, + value?: string, + }> = new Action(); updateTheme: Action<{ theme: string }> = new Action(); + finishInitialScreenSettings: Action = new Action(); } diff --git a/source/renderer/app/api/transactions/types.js b/source/renderer/app/api/transactions/types.js index 66517d23b6..8b62450f90 100644 --- a/source/renderer/app/api/transactions/types.js +++ b/source/renderer/app/api/transactions/types.js @@ -20,6 +20,7 @@ export type Transaction = { tag: 'applying' | 'inNewestBlocks' | 'persisted' | 'wontApply' | 'creating', data: {}, }, + currentTimeFormat: string, }; export type PaymentDistribution = { diff --git a/source/renderer/app/api/utils/localStorage.js b/source/renderer/app/api/utils/localStorage.js index 107122df1c..316d45f697 100644 --- a/source/renderer/app/api/utils/localStorage.js +++ b/source/renderer/app/api/utils/localStorage.js @@ -18,12 +18,7 @@ export type WalletsLocalData = { }; type StorageKeys = { - USER_LOCALE: string, - TERMS_OF_USE_ACCEPTANCE: string, - THEME: string, - DATA_LAYER_MIGRATION_ACCEPTANCE: string, - READ_NEWS: string, - WALLETS: string, + [key: string]: string, }; /** @@ -32,173 +27,159 @@ type StorageKeys = { */ export default class LocalStorageApi { - storageKeys: StorageKeys; - - constructor(NETWORK: string) { - this.storageKeys = { - USER_LOCALE: `${NETWORK}-USER-LOCALE`, - TERMS_OF_USE_ACCEPTANCE: `${NETWORK}-TERMS-OF-USE-ACCEPTANCE`, - THEME: `${NETWORK}-THEME`, - DATA_LAYER_MIGRATION_ACCEPTANCE: `${NETWORK}-DATA-LAYER-MIGRATION-ACCEPTANCE`, - READ_NEWS: `${NETWORK}-READ_NEWS`, - WALLETS: `${NETWORK}-WALLETS`, - }; - } - - getUserLocale = (): Promise => + static Getter = (key: string, fallbackValue: any): Promise => new Promise((resolve, reject) => { try { - const locale = store.get(this.storageKeys.USER_LOCALE); - if (!locale) return resolve(''); - resolve(locale); + const value = store.get(key); + if (!value) return resolve(fallbackValue); + resolve(value); } catch (error) { return reject(error); } }); - setUserLocale = (locale: string): Promise => + static Setter = (key: string, value: any): Promise => new Promise((resolve, reject) => { try { - store.set(this.storageKeys.USER_LOCALE, locale); + store.set(key, value); resolve(); } catch (error) { return reject(error); } }); - unsetUserLocale = (): Promise => + static Unsetter = (key: string): Promise => new Promise(resolve => { try { - store.delete(this.storageKeys.USER_LOCALE); + store.delete(key); resolve(); } catch (error) {} // eslint-disable-line }); - getTermsOfUseAcceptance = (): Promise => - new Promise((resolve, reject) => { - try { - const accepted = store.get(this.storageKeys.TERMS_OF_USE_ACCEPTANCE); - if (!accepted) return resolve(false); - resolve(accepted); - } catch (error) { - return reject(error); - } + storageKeys: StorageKeys; + + constructor(NETWORK: string) { + const storageKeysRaw = [ + 'USER_LOCALE', + 'USER_NUMBER_FORMAT', + 'USER_DATE_FORMAT_ENGLISH', + 'USER_DATE_FORMAT_JAPANESE', + 'USER_TIME_FORMAT', + 'TERMS_OF_USE_ACCEPTANCE', + 'THEME', + 'DATA_LAYER_MIGRATION_ACCEPTANCE', + 'READ_NEWS', + 'WALLETS', + ]; + this.storageKeys = {}; + storageKeysRaw.forEach(key => { + const keyStr = key.replace(new RegExp('_', 'g'), '-'); + this.storageKeys[key] = `${NETWORK}-${keyStr}`; }); + } + + getUserLocale = (): Promise => + new LocalStorageApi.Getter(this.storageKeys.USER_LOCALE, ''); + + setUserLocale = (locale: string): Promise => + new LocalStorageApi.Setter(this.storageKeys.USER_LOCALE, locale); + + unsetUserLocale = (): Promise => + new LocalStorageApi.Unsetter(this.storageKeys.USER_LOCALE); + + getUserNumberFormat = (): Promise => + new LocalStorageApi.Getter(this.storageKeys.USER_NUMBER_FORMAT, ''); + + setUserNumberFormat = (numberFormat: string): Promise => + new LocalStorageApi.Setter( + this.storageKeys.USER_NUMBER_FORMAT, + numberFormat + ); + + unsetUserNumberFormat = (): Promise => + new LocalStorageApi.Unsetter(this.storageKeys.USER_NUMBER_FORMAT); + + getUserDateFormatEnglish = (): Promise => + new LocalStorageApi.Getter(this.storageKeys.USER_DATE_FORMAT_ENGLISH, ''); + + setUserDateFormatEnglish = (dateFormat: string): Promise => + new LocalStorageApi.Setter( + this.storageKeys.USER_DATE_FORMAT_ENGLISH, + dateFormat + ); + + unsetUserDateFormatEnglish = (): Promise => + new LocalStorageApi.Unsetter(this.storageKeys.USER_DATE_FORMAT_ENGLISH); + + getUserDateFormatJapanese = (): Promise => + new LocalStorageApi.Getter(this.storageKeys.USER_DATE_FORMAT_JAPANESE, ''); + + setUserDateFormatJapanese = (dateFormat: string): Promise => + new LocalStorageApi.Setter( + this.storageKeys.USER_DATE_FORMAT_JAPANESE, + dateFormat + ); + + unsetUserDateFormatJapanese = (): Promise => + new LocalStorageApi.Unsetter(this.storageKeys.USER_DATE_FORMAT_JAPANESE); + + getUserTimeFormat = (): Promise => + new LocalStorageApi.Getter(this.storageKeys.USER_TIME_FORMAT, ''); + + setUserTimeFormat = (timeFormat: string): Promise => + new LocalStorageApi.Setter(this.storageKeys.USER_TIME_FORMAT, timeFormat); + + unsetUserTimeFormat = (): Promise => + new LocalStorageApi.Unsetter(this.storageKeys.USER_TIME_FORMAT); + + getTermsOfUseAcceptance = (): Promise => + new LocalStorageApi.Getter(this.storageKeys.TERMS_OF_USE_ACCEPTANCE, false); setTermsOfUseAcceptance = (): Promise => - new Promise((resolve, reject) => { - try { - store.set(this.storageKeys.TERMS_OF_USE_ACCEPTANCE, true); - resolve(); - } catch (error) { - return reject(error); - } - }); + new LocalStorageApi.Setter(this.storageKeys.TERMS_OF_USE_ACCEPTANCE, true); unsetTermsOfUseAcceptance = (): Promise => - new Promise(resolve => { - try { - store.delete(this.storageKeys.TERMS_OF_USE_ACCEPTANCE); - resolve(); - } catch (error) {} // eslint-disable-line - }); + new LocalStorageApi.Unsetter(this.storageKeys.TERMS_OF_USE_ACCEPTANCE); getUserTheme = (): Promise => - new Promise((resolve, reject) => { - try { - const theme = store.get(this.storageKeys.THEME); - if (!theme) return resolve(''); - resolve(theme); - } catch (error) { - return reject(error); - } - }); + new LocalStorageApi.Getter(this.storageKeys.THEME, ''); setUserTheme = (theme: string): Promise => - new Promise((resolve, reject) => { - try { - store.set(this.storageKeys.THEME, theme); - resolve(); - } catch (error) { - return reject(error); - } - }); + new LocalStorageApi.Setter(this.storageKeys.THEME, theme); unsetUserTheme = (): Promise => - new Promise(resolve => { - try { - store.delete(this.storageKeys.THEME); - resolve(); - } catch (error) {} // eslint-disable-line - }); + new LocalStorageApi.Unsetter(this.storageKeys.THEME); getDataLayerMigrationAcceptance = (): Promise => - new Promise((resolve, reject) => { - try { - const accepted = store.get( - this.storageKeys.DATA_LAYER_MIGRATION_ACCEPTANCE - ); - if (!accepted) return resolve(false); - resolve(true); - } catch (error) { - return reject(error); - } - }); + new LocalStorageApi.Getter( + this.storageKeys.DATA_LAYER_MIGRATION_ACCEPTANCE, + false + ); setDataLayerMigrationAcceptance = (): Promise => - new Promise((resolve, reject) => { - try { - store.set(this.storageKeys.DATA_LAYER_MIGRATION_ACCEPTANCE, true); - resolve(); - } catch (error) { - return reject(error); - } - }); + new LocalStorageApi.Setter( + this.storageKeys.DATA_LAYER_MIGRATION_ACCEPTANCE, + true + ); unsetDataLayerMigrationAcceptance = (): Promise => - new Promise(resolve => { - try { - store.delete(this.storageKeys.DATA_LAYER_MIGRATION_ACCEPTANCE); - resolve(); - } catch (error) {} // eslint-disable-line - }); + new LocalStorageApi.Unsetter( + this.storageKeys.DATA_LAYER_MIGRATION_ACCEPTANCE + ); getWalletsLocalData = (): Promise => - new Promise((resolve, reject) => { - try { - const walletsLocalData = store.get(this.storageKeys.WALLETS); - if (!walletsLocalData) return resolve({}); - return resolve(walletsLocalData); - } catch (error) { - return reject(error); - } - }); + new LocalStorageApi.Getter(this.storageKeys.THEME, {}); getWalletLocalData = (walletId: string): Promise => - new Promise((resolve, reject) => { - try { - const walletData = store.get(`${this.storageKeys.WALLETS}.${walletId}`); - if (!walletData) { - resolve({ - id: walletId, - }); - } - return resolve(walletData); - } catch (error) { - return reject(error); - } + new LocalStorageApi.Getter(`${this.storageKeys.WALLETS}.${walletId}`, { + id: walletId, }); setWalletLocalData = (walletData: WalletLocalData): Promise => - new Promise((resolve, reject) => { - try { - const walletId = walletData.id; - store.set(`${this.storageKeys.WALLETS}.${walletId}`, walletData); - return resolve(); - } catch (error) { - return reject(error); - } - }); + new LocalStorageApi.Setter( + `${this.storageKeys.WALLETS}.${walletData.id}`, + walletData + ); updateWalletLocalData = (updatedWalletData: Object): Promise => new Promise(async (resolve, reject) => { @@ -218,25 +199,10 @@ export default class LocalStorageApi { }); unsetWalletLocalData = (walletId: string): Promise => - new Promise((resolve, reject) => { - try { - store.delete(`${this.storageKeys.WALLETS}.${walletId}`); - return resolve(); - } catch (error) { - return reject(error); - } - }); + new LocalStorageApi.Unsetter(`${this.storageKeys.WALLETS}.${walletId}`); getReadNews = (): Promise => - new Promise((resolve, reject) => { - try { - const readNews = store.get(this.storageKeys.READ_NEWS); - if (!readNews) return resolve([]); - resolve(readNews); - } catch (error) { - return reject(error); - } - }); + new LocalStorageApi.Getter(this.storageKeys.READ_NEWS, []); markNewsAsRead = ( newsTimestamps: NewsTimestamp[] @@ -259,15 +225,14 @@ export default class LocalStorageApi { }); unsetReadNews = (): Promise => - new Promise(resolve => { - try { - store.delete(this.storageKeys.READ_NEWS); - resolve(); - } catch (error) {} // eslint-disable-line - }); + new LocalStorageApi.Unsetter(this.storageKeys.READ_NEWS); reset = async () => { await this.unsetUserLocale(); + await this.unsetUserNumberFormat(); + await this.unsetUserDateFormatEnglish(); + await this.unsetUserDateFormatJapanese(); + await this.unsetUserTimeFormat(); await this.unsetTermsOfUseAcceptance(); await this.unsetUserTheme(); await this.unsetDataLayerMigrationAcceptance(); diff --git a/source/renderer/app/components/news/AlertsOverlay.js b/source/renderer/app/components/news/AlertsOverlay.js index 84c2161a62..129a21ae5c 100644 --- a/source/renderer/app/components/news/AlertsOverlay.js +++ b/source/renderer/app/components/news/AlertsOverlay.js @@ -22,6 +22,7 @@ type Props = { onOpenExternalLink: Function, allAlertsCount: number, hideCounter?: boolean, + currentDateFormat: string, }; @observer @@ -33,12 +34,6 @@ export default class AlertsOverlay extends Component { }; } - localizedDateFormat: 'MM/DD/YYYY'; - - componentWillMount() { - this.localizedDateFormat = moment.localeData().longDateFormat('L'); - } - contentClickHandler(event: SyntheticMouseEvent) { const linkUrl = get(event, ['target', 'href']); if (linkUrl) { @@ -87,7 +82,7 @@ export default class AlertsOverlay extends Component { render() { const { showOverlay } = this.state; - const { alerts } = this.props; + const { alerts, currentDateFormat } = this.props; const [alert] = alerts; const { content, date, action, title } = alert; return ( @@ -101,7 +96,7 @@ export default class AlertsOverlay extends Component { {this.renderCounter(alerts)}

{title}

- {moment(date).format(this.localizedDateFormat)} + {moment(date).format(currentDateFormat)}
{ - localizedDateFormat: 'MM/DD/YYYY'; - - componentWillMount() { - this.localizedDateFormat = moment.localeData().longDateFormat('L'); - } - contentClickHandler(event: SyntheticMouseEvent) { const linkUrl = get(event, ['target', 'href']); if (linkUrl) { @@ -46,13 +41,13 @@ export default class IncidentOverlay extends Component { }; render() { - const { incident } = this.props; + const { incident, currentDateFormat } = this.props; const { content, date, action, title } = incident; return (

{title}

- {moment(date).format(this.localizedDateFormat)} + {moment(date).format(currentDateFormat)}
{ onOpenExternalLink, openWithoutTransition, onGoToRoute, + currentDateFormat, } = this.props; const { hasShadow } = this.state; @@ -150,6 +152,7 @@ export default class NewsFeed extends Component { onOpenExternalLink={onOpenExternalLink} onOpenAlert={onOpenAlert} onGoToRoute={onGoToRoute} + currentDateFormat={currentDateFormat} /> ))}
diff --git a/source/renderer/app/components/news/NewsItem.js b/source/renderer/app/components/news/NewsItem.js index 24800b0797..03f9009fbb 100644 --- a/source/renderer/app/components/news/NewsItem.js +++ b/source/renderer/app/components/news/NewsItem.js @@ -19,6 +19,7 @@ type Props = { onGoToRoute: Function, expandWithoutTransition?: boolean, isNewsFeedOpen: boolean, + currentDateFormat: string, }; type State = { @@ -33,8 +34,6 @@ export default class NewsItem extends Component { expandWithoutTransition: false, }; - localizedDateFormat: 'MM/DD/YYYY'; - state = { newsItemExpanded: false, newsItemCollapsible: true, @@ -51,10 +50,6 @@ export default class NewsItem extends Component { } } - componentWillMount() { - this.localizedDateFormat = moment.localeData().longDateFormat('L'); - } - newsItemClickHandler(event: SyntheticMouseEvent) { const linkUrl = get(event, ['target', 'href']); if (linkUrl) { @@ -114,7 +109,7 @@ export default class NewsItem extends Component { }; render() { - const { newsItem, expandWithoutTransition } = this.props; + const { newsItem, expandWithoutTransition, currentDateFormat } = this.props; const componentClasses = classNames([ styles.component, newsItem.type ? styles[newsItem.type] : null, @@ -132,7 +127,7 @@ export default class NewsItem extends Component { > {title}
- {moment(newsItem.date).format(this.localizedDateFormat)} + {moment(newsItem.date).format(currentDateFormat)}
{ + static defaultProps = { + error: null, + }; + + render() { + const { + onChangeItem, + onSubmit, + currentLocale, + currentNumberFormat, + currentDateFormat, + currentTimeFormat, + error, + } = this.props; + return ( +
+
+ +
+
+ ); + } +} diff --git a/source/renderer/app/components/profile/language-selection/LanguageSelectionForm.scss b/source/renderer/app/components/profile/initial-settings/InitialSettings.scss similarity index 65% rename from source/renderer/app/components/profile/language-selection/LanguageSelectionForm.scss rename to source/renderer/app/components/profile/initial-settings/InitialSettings.scss index 5b16b7e2d2..2c4a7e447c 100644 --- a/source/renderer/app/components/profile/language-selection/LanguageSelectionForm.scss +++ b/source/renderer/app/components/profile/initial-settings/InitialSettings.scss @@ -15,18 +15,3 @@ padding: 30px 30px 20px; width: 620px; } - -.languageSelect { - margin-bottom: 30px; -} - -.submitButton { - display: block !important; - margin: 0 auto; -} - -.error { - @include error-message; - margin-bottom: 1rem; - text-align: center; -} diff --git a/source/renderer/app/components/profile/language-selection/LanguageSelectionForm.js b/source/renderer/app/components/profile/language-selection/LanguageSelectionForm.js deleted file mode 100644 index 4ccaa63751..0000000000 --- a/source/renderer/app/components/profile/language-selection/LanguageSelectionForm.js +++ /dev/null @@ -1,104 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import classnames from 'classnames'; -import { observer } from 'mobx-react'; -import { Button } from 'react-polymorph/lib/components/Button'; -import { Select } from 'react-polymorph/lib/components/Select'; -import { ButtonSpinnerSkin } from 'react-polymorph/lib/skins/simple/ButtonSpinnerSkin'; -import { SelectSkin } from 'react-polymorph/lib/skins/simple/SelectSkin'; -import { defineMessages, intlShape } from 'react-intl'; -import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; -import LocalizableError from '../../../i18n/LocalizableError'; -import styles from './LanguageSelectionForm.scss'; -import type { ReactIntlMessage } from '../../../types/i18nTypes'; - -const messages = defineMessages({ - languageSelectLabel: { - id: 'profile.languageSelect.form.languageSelectLabel', - defaultMessage: '!!!Select your language', - description: 'Label for the language select.', - }, - submitLabel: { - id: 'profile.languageSelect.form.submitLabel', - defaultMessage: '!!!Continue', - description: 'Label for the "Language select" form submit button.', - }, -}); - -type Props = { - languages: Array<{ value: string, label: ReactIntlMessage }>, - onSubmit: Function, - isSubmitting: boolean, - preselectedLanguage: string, - error?: ?LocalizableError, -}; - -@observer -export default class LanguageSelectionForm extends Component { - static defaultProps = { - error: null, - }; - - static contextTypes = { - intl: intlShape.isRequired, - }; - - submit = () => { - this.form.submit({ - onSuccess: form => { - const { languageId } = form.values(); - this.props.onSubmit({ locale: languageId }); - }, - onError: () => {}, - }); - }; - - form = new ReactToolboxMobxForm( - { - fields: { - languageId: { - label: this.context.intl.formatMessage(messages.languageSelectLabel), - value: this.props.preselectedLanguage, - }, - }, - }, - { - options: { - validateOnChange: false, - }, - } - ); - - render() { - const { intl } = this.context; - const { form } = this; - const { languages, isSubmitting, error } = this.props; - const languageId = form.$('languageId'); - const languageOptions = languages.map(language => ({ - value: language.value, - label: intl.formatMessage(language.label), - })); - return ( -
-
- - - {error &&

{error}

} -
+ ); } } diff --git a/source/renderer/app/components/settings/categories/GeneralSettings.scss b/source/renderer/app/components/settings/categories/GeneralSettings.scss index 74bb3e7b1a..04c750a946 100644 --- a/source/renderer/app/components/settings/categories/GeneralSettings.scss +++ b/source/renderer/app/components/settings/categories/GeneralSettings.scss @@ -4,6 +4,10 @@ margin-bottom: 20px; } +.select { + margin-bottom: 20px; +} + .error { @include error-message; text-align: center; diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationSetupWizardDialog.js b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationSetupWizardDialog.js index 9572174eaf..00579e79c2 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationSetupWizardDialog.js +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationSetupWizardDialog.js @@ -1,6 +1,7 @@ // @flow import React, { Component } from 'react'; import { observer } from 'mobx-react'; +import { BigNumber } from 'bignumber.js'; import { get } from 'lodash'; import DelegationStepsActivationDialog from './DelegationStepsActivationDialog'; import DelegationStepsChooseWalletDialog from './DelegationStepsChooseWalletDialog'; @@ -109,6 +110,7 @@ export default class DelegationSetupWizardDialog extends Component { case 3: content = ( { case 4: content = ( { render() { const { form } = this; const { intl } = this.context; - const { isSpendingPasswordSet, onBack, onClose, stepsList } = this.props; + const { + isSpendingPasswordSet, + onBack, + onClose, + stepsList, + amount, + fees, + } = this.props; const spendingPasswordField = form.$('spendingPassword'); @@ -248,7 +259,8 @@ export default class DelegationStepsActivationDialog extends Component { {intl.formatMessage(messages.amountLabel)}

- 3 ADA + {formattedWalletAmount(amount, false)} + ADA

@@ -257,7 +269,8 @@ export default class DelegationStepsActivationDialog extends Component { {intl.formatMessage(messages.feesLabel)}

- + 12.042481 ADA + + {formattedWalletAmount(fees, false)} + ADA

@@ -267,7 +280,8 @@ export default class DelegationStepsActivationDialog extends Component { {intl.formatMessage(messages.totalLabel)}

- 15.042481 ADA + {formattedWalletAmount(amount.add(fees), false)} + ADA

diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.js b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.js index 684b56a739..33a4a1b32b 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.js +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.js @@ -7,6 +7,7 @@ import { Stepper } from 'react-polymorph/lib/components/Stepper'; import { StepperSkin } from 'react-polymorph/lib/skins/simple/StepperSkin'; import { Input } from 'react-polymorph/lib/components/Input'; import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin'; +import BigNumber from 'bignumber.js'; import commonStyles from './DelegationSteps.scss'; import styles from './DelegationStepsConfirmationDialog.scss'; import DialogCloseButton from '../../widgets/DialogCloseButton'; @@ -16,6 +17,7 @@ import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import { submitOnEnter } from '../../../utils/form'; import globalMessages from '../../../i18n/global-messages'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig'; +import { formattedWalletAmount } from '../../../utils/formatters'; const messages = defineMessages({ title: { @@ -76,6 +78,7 @@ type Props = { onBack: Function, onClose: Function, onConfirm: Function, + fees: BigNumber, stepsList: Array, }; @@ -142,7 +145,13 @@ export default class DelegationStepsConfirmationDialog extends Component render() { const { form } = this; const { intl } = this.context; - const { isSpendingPasswordSet, onBack, onClose, stepsList } = this.props; + const { + isSpendingPasswordSet, + onBack, + onClose, + stepsList, + fees, + } = this.props; const spendingPasswordField = form.$('spendingPassword'); @@ -206,7 +215,8 @@ export default class DelegationStepsConfirmationDialog extends Component {intl.formatMessage(messages.feesLabel)}

- 0.172081 ADA + {formattedWalletAmount(fees, false)} + ADA

diff --git a/source/renderer/app/components/staking/rewards/StakingRewards.js b/source/renderer/app/components/staking/rewards/StakingRewards.js index 4b0594de72..209765ee88 100644 --- a/source/renderer/app/components/staking/rewards/StakingRewards.js +++ b/source/renderer/app/components/staking/rewards/StakingRewards.js @@ -5,12 +5,14 @@ import { defineMessages, intlShape } from 'react-intl'; import SVGInline from 'react-svg-inline'; import { get, map, orderBy } from 'lodash'; import classNames from 'classnames'; +import { BigNumber } from 'bignumber.js'; import BorderedBox from '../../widgets/BorderedBox'; import LoadingSpinner from '../../widgets/LoadingSpinner'; import sortIcon from '../../../assets/images/ascending.inline.svg'; import externalLinkIcon from '../../../assets/images/link-ic.inline.svg'; import type { Reward } from '../../../api/staking/types'; import styles from './StakingRewards.scss'; +import { SIMPLE_DECIMAL_PLACES_IN_ADA } from '../../../config/numbersConfig'; const messages = defineMessages({ title: { @@ -192,7 +194,12 @@ export default class StakingRewards extends Component {

{rewardWallet} - {rewardAmount} ADA + + {new BigNumber(rewardAmount).toFormat( + SIMPLE_DECIMAL_PLACES_IN_ADA + )}{' '} + ADA + ); })} diff --git a/source/renderer/app/components/status/DaedalusDiagnostics.js b/source/renderer/app/components/status/DaedalusDiagnostics.js index 44f0578e4d..5f28c3f8bc 100644 --- a/source/renderer/app/components/status/DaedalusDiagnostics.js +++ b/source/renderer/app/components/status/DaedalusDiagnostics.js @@ -9,6 +9,7 @@ import CopyToClipboard from 'react-copy-to-clipboard'; import { Tooltip } from 'react-polymorph/lib/components/Tooltip'; import { TooltipSkin } from 'react-polymorph/lib/skins/simple/TooltipSkin'; import SVGInline from 'react-svg-inline'; +import { BigNumber } from 'bignumber.js'; import { ALLOWED_TIME_DIFFERENCE, MAX_ALLOWED_STALL_DURATION, @@ -759,15 +760,24 @@ export default class DaedalusDiagnostics extends Component { {intl.formatMessage(messages.syncPercentage)}: - {syncPercentage.toFixed(2)}% + + {new BigNumber( + parseFloat(syncPercentage).toFixed(2) + ).toFormat(2)} + % + {intl.formatMessage(messages.networkBlockHeight)}: - {networkBlockHeight} + + {new BigNumber(parseInt(networkBlockHeight, 10)).toFormat(0)} + {intl.formatMessage(messages.localBlockHeight)}: - {localBlockHeight} + + {new BigNumber(parseInt(localBlockHeight, 10)).toFormat(0)} + {intl.formatMessage(messages.remainingUnsyncedBlocks)}: @@ -779,7 +789,9 @@ export default class DaedalusDiagnostics extends Component { {intl.formatMessage(messages.latestLocalBlockAge)}: {latestLocalBlockTimestamp > 0 - ? `${latestLocalBlockAge} ms` + ? `${new BigNumber( + parseInt(latestLocalBlockAge, 10) + ).toFormat(0)} ms` : '-'} @@ -787,7 +799,9 @@ export default class DaedalusDiagnostics extends Component { {intl.formatMessage(messages.latestNetworkBlockAge)}: {latestNetworkBlockTimestamp > 0 - ? `${latestNetworkBlockAge} ms` + ? `${new BigNumber( + parseInt(latestNetworkBlockAge, 10) + ).toFormat(0)} ms` : '-'} @@ -806,7 +820,9 @@ export default class DaedalusDiagnostics extends Component { {isNTPServiceReachable - ? `${localTimeDifference || 0} μs` + ? `${new BigNumber( + parseInt(localTimeDifference, 10) + ).toFormat(0) || 0} μs` : intl.formatMessage(messages.serviceUnreachable)} diff --git a/source/renderer/app/components/wallet/transactions/Transaction.js b/source/renderer/app/components/wallet/transactions/Transaction.js index c33966949a..f70ea17690 100644 --- a/source/renderer/app/components/wallet/transactions/Transaction.js +++ b/source/renderer/app/components/wallet/transactions/Transaction.js @@ -138,6 +138,7 @@ type Props = { network: string, onDetailsToggled: ?Function, onOpenExternalLink: ?Function, + currentTimeFormat: string, }; export default class Transaction extends Component { @@ -169,6 +170,7 @@ export default class Transaction extends Component { onOpenExternalLink, isRestoreActive, isExpanded, + currentTimeFormat, } = this.props; const { intl } = this.context; @@ -261,7 +263,7 @@ export default class Transaction extends Component {
{intl.formatMessage(messages.type, { currency })},{' '} - {moment(data.date).format('hh:mm:ss A')} + {moment(data.date).format(currentTimeFormat)}
{transactionStateTag()}
diff --git a/source/renderer/app/components/wallet/transactions/WalletTransactionsList.js b/source/renderer/app/components/wallet/transactions/WalletTransactionsList.js index 07193d4905..1f379080dd 100644 --- a/source/renderer/app/components/wallet/transactions/WalletTransactionsList.js +++ b/source/renderer/app/components/wallet/transactions/WalletTransactionsList.js @@ -57,6 +57,8 @@ type Props = { showMoreTransactionsButton?: boolean, transactions: Array, walletId: string, + currentTimeFormat: string, + currentDateFormat: string, }; const DATE_FORMAT = 'YYYY-MM-DD'; @@ -79,14 +81,6 @@ export default class WalletTransactionsList extends Component { virtualList: ?VirtualTransactionList; simpleList: ?SimpleTransactionList; loadingSpinner: ?LoadingSpinner; - localizedDateFormat: 'MM/DD/YYYY'; - - componentWillMount() { - this.localizedDateFormat = moment.localeData().longDateFormat('L'); - // Localized dateFormat: - // English - MM/DD/YYYY - // Japanese - YYYY/MM/DD - } groupTransactionsByDay( transactions: Array @@ -123,6 +117,7 @@ export default class WalletTransactionsList extends Component { localizedDate(date: string) { const { intl } = this.context; + const { currentDateFormat } = this.props; // TODAY const today = moment().format(DATE_FORMAT); if (date === today) return intl.formatMessage(messages.today); @@ -132,7 +127,7 @@ export default class WalletTransactionsList extends Component { .format(DATE_FORMAT); if (date === yesterday) return intl.formatMessage(messages.yesterday); // PAST DATE - return moment(date).format(this.localizedDateFormat); + return moment(date).format(currentDateFormat); } isTxExpanded = (tx: WalletTransaction) => @@ -185,6 +180,7 @@ export default class WalletTransactionsList extends Component { isRestoreActive, network, onOpenExternalLink, + currentTimeFormat, } = this.props; const { isFirstInGroup, isLastInGroup, tx } = data; const txClasses = classnames([ @@ -205,6 +201,7 @@ export default class WalletTransactionsList extends Component { onDetailsToggled={() => this.toggleTransactionExpandedState(tx)} onOpenExternalLink={onOpenExternalLink} state={tx.state} + currentTimeFormat={currentTimeFormat} /> ); diff --git a/source/renderer/app/components/widgets/forms/ProfileSettingsForm.js b/source/renderer/app/components/widgets/forms/ProfileSettingsForm.js new file mode 100644 index 0000000000..9e7ced4134 --- /dev/null +++ b/source/renderer/app/components/widgets/forms/ProfileSettingsForm.js @@ -0,0 +1,142 @@ +// @flow +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import classNames from 'classnames'; +import { Button } from 'react-polymorph/lib/components/Button'; +import { Select } from 'react-polymorph/lib/components/Select'; +import { ButtonSpinnerSkin } from 'react-polymorph/lib/skins/simple/ButtonSpinnerSkin'; +import { SelectSkin } from 'react-polymorph/lib/skins/simple/SelectSkin'; +import { defineMessages, intlShape } from 'react-intl'; +import LocalizableError from '../../../i18n/LocalizableError'; +import styles from './ProfileSettingsForm.scss'; +import { + LANGUAGE_OPTIONS, + NUMBER_OPTIONS, + DATE_ENGLISH_OPTIONS, + DATE_JAPANESE_OPTIONS, + TIME_OPTIONS, + PROFILE_SETTINGS, +} from '../../../config/profileConfig'; + +const messages = defineMessages({ + localeSelectLabel: { + id: 'profile.settings.languageSelect.label', + defaultMessage: '!!!Language', + description: 'Label for the language select.', + }, + numberFormatSelectLabel: { + id: 'profile.settings.numberSelect.label', + defaultMessage: '!!!Number format', + description: 'Label for the number select.', + }, + dateFormatSelectLabel: { + id: 'profile.settings.dateSelect.label', + defaultMessage: '!!!Date format', + description: 'Label for the date select.', + }, + timeFormatSelectLabel: { + id: 'profile.settings.timeSelect.label', + defaultMessage: '!!!Time format', + description: 'Label for the time select.', + }, + submitLabel: { + id: 'profile.settings.submitLabel', + defaultMessage: '!!!Continue', + description: 'Label for the "Language select" form submit button.', + }, +}); + +export type ProfileSettingsFormProps = { + currentLocale: string, + currentNumberFormat: string, + currentDateFormat: string, + currentTimeFormat: string, + onChangeItem: Function, + onSubmit?: Function, + isSubmitting?: boolean, + error?: ?LocalizableError, +}; + +@observer +export default class ProfileSettingsForm extends Component { + static defaultProps = { + onChangeItem: () => {}, + }; + + static contextTypes = { + intl: intlShape.isRequired, + }; + + get locale() { + const { props, context } = this; + const options = LANGUAGE_OPTIONS.map(language => ({ + value: language.value, + label: context.intl.formatMessage(language.label), + })); + const value = props.currentLocale; + return { value, options }; + } + + get numberFormat() { + return { + options: NUMBER_OPTIONS, + value: this.props.currentNumberFormat, + }; + } + + get dateFormat() { + const { currentLocale, currentDateFormat } = this.props; + return { + options: + currentLocale === 'en-US' + ? DATE_ENGLISH_OPTIONS + : DATE_JAPANESE_OPTIONS, + value: currentDateFormat, + }; + } + + get timeFormat() { + return { + options: TIME_OPTIONS, + value: this.props.currentTimeFormat, + }; + } + + getSelect = (id: string) => { + const { formatMessage } = this.context.intl; + const { onChangeItem } = this.props; + const { value, options } = (this: any)[id]; + return ( +