Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 37 additions & 60 deletions src/components/ModalAddressMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { t } from 'ttag';
import { getGlobalWallet } from '../modules/wallet';
import helpers from '../utils/helpers';
import { ADDRESS_MODE } from '../constants';
import RadioGroup from './Radio/RadioGroup';
import PreferenceSaveButton from './PreferenceSaveButton';

const LEARN_MORE_URL = 'https://docs.hathor.network/explanations/features/wallet-service/address-mode';

Expand Down Expand Up @@ -66,78 +68,53 @@ function ModalAddressMode({ currentMode, onSave, onClose }) {
<span aria-hidden="true">&times;</span>
</button>
</div>
<div className="modal-body" style={{ display: 'flex', flexDirection: 'column' }}>
<p style={{ marginBottom: 40 }}>
<div className="modal-body d-flex flex-column">
<p className="address-mode-intro">
{t`You can set your wallet to `}<strong>{t`single or multi address mode`}</strong>{'.'}
<br />
{t`You can switch anytime in the settings.`}
</p>

<div>
<div
className={`address-mode-option ${selectedMode === ADDRESS_MODE.SINGLE ? 'address-mode-option--selected' : ''} ${isSingleDisabled ? 'address-mode-option--disabled' : ''}`}
onClick={() => !isSingleDisabled && setSelectedMode(ADDRESS_MODE.SINGLE)}
>
<div className="d-flex align-items-center mb-2" style={{ gap: '6px' }}>
<input
type="radio"
name="addressMode"
checked={selectedMode === ADDRESS_MODE.SINGLE}
disabled={isSingleDisabled}
onChange={() => setSelectedMode(ADDRESS_MODE.SINGLE)}
/>
<strong className="address-mode-label">{t`Single Address`}</strong>
</div>
<p className="address-mode-description">
{t`Your wallet will use only one address (index 0).`}
<br />
<span className="address-mode-hint">{t`Easier to manage and compatible with all dApps.`}</span>
</p>
</div>
<RadioGroup
name="addressMode"
value={selectedMode}
onChange={setSelectedMode}
options={[
{
value: ADDRESS_MODE.SINGLE,
title: t`Single Address`,
disabled: isSingleDisabled,
description: t`Your wallet will use only one address (index 0).`,
hint: t`Easier to manage and compatible with all dApps.`,
},
{
value: ADDRESS_MODE.MULTI,
title: t`Multi Address`,
description: t`Your wallet will let you generate multiple addresses.`,
hint: t`Useful for advanced users.`,
},
]}
/>

<div>
<div
className={`address-mode-option ${selectedMode === ADDRESS_MODE.MULTI ? 'address-mode-option--selected' : ''}`}
onClick={() => setSelectedMode(ADDRESS_MODE.MULTI)}
>
<div className="d-flex align-items-center mb-2" style={{ gap: '6px' }}>
<input
type="radio"
name="addressMode"
checked={selectedMode === ADDRESS_MODE.MULTI}
onChange={() => setSelectedMode(ADDRESS_MODE.MULTI)}
/>
<strong className="address-mode-label">{t`Multi Address`}</strong>
</div>
<p className="address-mode-description">
{t`Your wallet will let you generate multiple addresses.`}
<br />
<span className="address-mode-hint">{t`Useful for advanced users.`}</span>
</p>
{hasTxOutside && (
<div className="address-mode-alert">
<i className="fa fa-exclamation-triangle" style={{ color: '#d97706' }}></i>
<span>
{t`You can't switch to single address mode because other addresses in your wallet are already in use.`}
{' '}
<a href="true" onClick={openLearnMore} style={{ textDecoration: 'underline', color: 'inherit' }}>{t`Learn more`}</a>{'.'}
</span>
</div>

{hasTxOutside && (
<div className="address-mode-alert">
<i className="fa fa-exclamation-triangle" style={{ color: '#d97706' }}></i>
<span>
{t`You can't switch to single address mode because other addresses in your wallet are already in use.`}
{' '}
<a href="true" onClick={openLearnMore} style={{ textDecoration: 'underline', color: 'inherit' }}>{t`Learn more`}</a>{'.'}
</span>
</div>
)}
</div>
)}
</div>
</div>
<div className="modal-footer justify-content-center" style={{ borderTop: 'none', marginTop: 12 }}>
<button
type="button"
className={`address-mode-save-btn ${hasChanged && !loading ? 'address-mode-save-btn--active' : ''}`}
<div className="modal-footer justify-content-center border-top-0 address-mode-footer">
<PreferenceSaveButton
title={t`Save preferences`}
disabled={!hasChanged || loading}
onClick={handleSave}
>
{t`Save preferences`}
</button>
/>
</div>
</div>
</div>
Expand Down
65 changes: 23 additions & 42 deletions src/components/ModalAmountFormat.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import $ from 'jquery';
import { t } from 'ttag';
import { AMOUNT_FORMAT } from '../constants';
import { compressAmountString } from '../utils/amount';
import RadioGroup from './Radio/RadioGroup';
import PreferenceSaveButton from './PreferenceSaveButton';

const MODAL_ID = 'amountFormatModal';

Expand Down Expand Up @@ -40,27 +42,6 @@ function ModalAmountFormat({ currentFormat, onSave, onClose }) {
return `${PREVIEW_EXPANDED} HTR`;
};

const renderOption = ({ format, title, description, isDefault }) => (
<div className="amount-format-option" onClick={() => setSelectedFormat(format)}>
<input
type="radio"
id={`amountFormat-${format}`}
name="amountFormat"
checked={selectedFormat === format}
onChange={() => setSelectedFormat(format)}
/>
<div className="amount-format-option-body">
<div className="amount-format-option-header">
<label className="amount-format-option-title" htmlFor={`amountFormat-${format}`}>
{title}
</label>
{isDefault && <span className="amount-format-tag">{t`Default`}</span>}
</div>
<p className="amount-format-option-description">{description}</p>
</div>
</div>
);

return (
<div className="modal fade amount-format-modal" id={MODAL_ID} tabIndex="-1" role="dialog" aria-labelledby={MODAL_ID} aria-hidden="true">
<div className="modal-dialog" role="document">
Expand All @@ -76,21 +57,24 @@ function ModalAmountFormat({ currentFormat, onSave, onClose }) {
{t`Choose how amounts are displayed across your wallet. You can change your default anytime.`}
</p>

<div className="amount-format-card">
{renderOption({
format: AMOUNT_FORMAT.EXPANDED,
title: t`Expanded`,
description: t`Standard notation. Leading zeros are written out in full (ex: 0.0000005195).`,
isDefault: true,
})}
<hr className="amount-format-divider" />
{renderOption({
format: AMOUNT_FORMAT.COMPRESSED,
title: t`Compressed`,
description: t`Compresses leading zeros into a subscript count for shorter, easier-to-read small values (ex: 0.0₆5195).`,
isDefault: false,
})}
</div>
<RadioGroup
name="amountFormat"
value={selectedFormat}
onChange={setSelectedFormat}
options={[
{
value: AMOUNT_FORMAT.EXPANDED,
title: t`Expanded`,
badge: t`Default`,
description: t`Standard notation. Leading zeros are written out in full (ex: 0.0000005195).`,
},
{
value: AMOUNT_FORMAT.COMPRESSED,
title: t`Compressed`,
description: t`Compresses leading zeros into a subscript count for shorter, easier-to-read small values (ex: 0.0₆5195).`,
},
]}
/>

<p className="amount-format-preview-label">{t`PREVIEW`}</p>
<div className="amount-format-preview">
Expand All @@ -99,14 +83,11 @@ function ModalAmountFormat({ currentFormat, onSave, onClose }) {
</div>
</div>
<div className="modal-footer justify-content-center">
<button
type="button"
className={`amount-format-save-btn ${hasChanged ? 'amount-format-save-btn--active' : ''}`}
<PreferenceSaveButton
title={t`Save preferences`}
disabled={!hasChanged}
onClick={() => onSave(selectedFormat)}
>
{t`Save preferences`}
</button>
/>
</div>
</div>
</div>
Expand Down
40 changes: 40 additions & 0 deletions src/components/PreferenceSaveButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import PropTypes from 'prop-types';

/**
* The primary save button shared by the preference modals. Renders only the
* button; the caller supplies its own container and spacing.
*
* @memberof Components
*/
function PreferenceSaveButton({ title, onClick, disabled }) {
return (
<button
type="button"
className={`preference-save-btn ${disabled ? '' : 'preference-save-btn--active'}`}
disabled={disabled}
onClick={onClick}
>
{title}
</button>
);
}

PreferenceSaveButton.propTypes = {
title: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired,
disabled: PropTypes.bool,
};

PreferenceSaveButton.defaultProps = {
disabled: false,
};

export default PreferenceSaveButton;
43 changes: 43 additions & 0 deletions src/components/Radio/RadioButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import PropTypes from 'prop-types';

/**
* The radio circle itself. A real input so keyboard and assistive tech work;
* `appearance: none` in SCSS replaces the native rendering.
*
* @memberof Components
*/
function RadioButton({ id, name, selected, disabled, onChange }) {
return (
<input
type="radio"
id={id}
name={name}
className="radio-button"
checked={selected}
disabled={disabled}
onChange={onChange}
/>
);
}

RadioButton.propTypes = {
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
selected: PropTypes.bool.isRequired,
disabled: PropTypes.bool,
onChange: PropTypes.func.isRequired,
};

RadioButton.defaultProps = {
disabled: false,
};

export default RadioButton;
55 changes: 55 additions & 0 deletions src/components/Radio/RadioGroup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import PropTypes from 'prop-types';
import RadioOption from './RadioOption';

/**
* A bordered card grouping radio options, separated by dividers. Controlled:
* the caller owns the selected value and receives it back through onChange.
*
* @memberof Components
*/
function RadioGroup({ name, value, onChange, options }) {
return (
<div className="radio-group">
{options.map((option, index) => (
<React.Fragment key={option.value}>
{index > 0 && <hr className="radio-group-divider" />}
<RadioOption
name={name}
value={option.value}
selected={value === option.value}
disabled={option.disabled}
onSelect={onChange}
title={option.title}
badge={option.badge}
description={option.description}
hint={option.hint}
/>
</React.Fragment>
))}
</div>
);
}

RadioGroup.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
options: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.string.isRequired,
title: PropTypes.node.isRequired,
description: PropTypes.node,
hint: PropTypes.node,
badge: PropTypes.node,
disabled: PropTypes.bool,
})).isRequired,
};

export default RadioGroup;
Loading