[redesign] navigation/wallet pages

This commit is contained in:
Sean Yesmunt 2018-01-23 14:09:21 -08:00
parent 2d72d1f663
commit f1c960c3c6
93 changed files with 1551 additions and 6033 deletions

View file

@ -18,6 +18,7 @@ module.name_mapper='^types\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/types\1'
module.name_mapper='^component\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/component\1' module.name_mapper='^component\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/component\1'
module.name_mapper='^page\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/page\1' module.name_mapper='^page\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/page\1'
module.name_mapper='^lbry\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/lbry\1' module.name_mapper='^lbry\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/lbry\1'
module.name_mapper='^rewards\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/rewards\1'
module.name_mapper='^modal\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/modal\1' module.name_mapper='^modal\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/modal\1'
[strict] [strict]

3
flow-typed/react-feather.js vendored Normal file
View file

@ -0,0 +1,3 @@
declare module 'react-feather' {
declare module.exports: any;
}

25
npm-debug.log.3540228334 Normal file
View file

@ -0,0 +1,25 @@
0 info it worked if it ends with ok
1 verbose cli [ '/Users/seanyesmunt/.nvm/versions/node/v6.12.0/bin/node',
1 verbose cli '/Users/seanyesmunt/.nvm/versions/node/v6.12.0/bin/npm',
1 verbose cli 'config',
1 verbose cli '--loglevel=warn',
1 verbose cli 'get',
1 verbose cli 'prefix' ]
2 info using npm@3.10.10
3 info using node@v6.12.0
4 verbose exit [ 0, true ]
5 verbose stack Error: write EPIPE
5 verbose stack at exports._errnoException (util.js:1020:11)
5 verbose stack at WriteWrap.afterWrite (net.js:800:14)
6 verbose cwd /Users/seanyesmunt/Workspace/lbry/lbry-app
7 error Darwin 17.3.0
8 error argv "/Users/seanyesmunt/.nvm/versions/node/v6.12.0/bin/node" "/Users/seanyesmunt/.nvm/versions/node/v6.12.0/bin/npm" "config" "--loglevel=warn" "get" "prefix"
9 error node v6.12.0
10 error npm v3.10.10
11 error code EPIPE
12 error errno EPIPE
13 error syscall write
14 error write EPIPE
15 error If you need help, you may report this error at:
15 error <https://github.com/npm/npm/issues>
16 verbose exit [ 1, true ]

25
npm-debug.log.3958211012 Normal file
View file

@ -0,0 +1,25 @@
0 info it worked if it ends with ok
1 verbose cli [ '/Users/seanyesmunt/.nvm/versions/node/v6.12.0/bin/node',
1 verbose cli '/Users/seanyesmunt/.nvm/versions/node/v6.12.0/bin/npm',
1 verbose cli 'config',
1 verbose cli '--loglevel=warn',
1 verbose cli 'get',
1 verbose cli 'prefix' ]
2 info using npm@3.10.10
3 info using node@v6.12.0
4 verbose exit [ 0, true ]
5 verbose stack Error: write EPIPE
5 verbose stack at exports._errnoException (util.js:1020:11)
5 verbose stack at WriteWrap.afterWrite (net.js:800:14)
6 verbose cwd /Users/seanyesmunt/Workspace/lbry/lbry-app
7 error Darwin 17.3.0
8 error argv "/Users/seanyesmunt/.nvm/versions/node/v6.12.0/bin/node" "/Users/seanyesmunt/.nvm/versions/node/v6.12.0/bin/npm" "config" "--loglevel=warn" "get" "prefix"
9 error node v6.12.0
10 error npm v3.10.10
11 error code EPIPE
12 error errno EPIPE
13 error syscall write
14 error write EPIPE
15 error If you need help, you may report this error at:
15 error <https://github.com/npm/npm/issues>
16 verbose exit [ 1, true ]

View file

@ -52,6 +52,7 @@
"rc-progress": "^2.0.6", "rc-progress": "^2.0.6",
"react": "^16.2.0", "react": "^16.2.0",
"react-dom": "^16.2.0", "react-dom": "^16.2.0",
"react-feather": "^1.0.8",
"react-markdown": "^2.5.0", "react-markdown": "^2.5.0",
"react-modal": "^3.1.7", "react-modal": "^3.1.7",
"react-paginate": "^5.0.0", "react-paginate": "^5.0.0",

View file

@ -1,52 +1,56 @@
import React from 'react'; // @flow
import PropTypes from 'prop-types'; import * as React from 'react';
import { clipboard } from 'electron'; import { clipboard } from 'electron';
import Link from 'component/link'; import { FormField } from 'component/common/form';
import classnames from 'classnames'; import Button from 'component/link';
export default class Address extends React.PureComponent { type Props = {
static propTypes = { address: string,
address: PropTypes.string, doShowSnackBar: ({ message: string }) => void,
}; };
constructor(props) { export default class Address extends React.PureComponent<Props> {
super(props); constructor() {
super();
this._inputElem = null; this.input = null;
} }
input: ?HTMLInputElement;
render() { render() {
const { address, showCopyButton, doShowSnackBar } = this.props; const { address, doShowSnackBar } = this.props;
return ( return (
<div className="form-field form-field--address"> <FormField
<input name="address"
className={classnames('input-copyable', { render={() => (
'input-copyable--with-copy-btn': showCopyButton, <React.Fragment>
})} <input
type="text" id="address"
ref={input => { className="input-copyable"
this._inputElem = input; readOnly
}} value={address || ''}
onFocus={() => { ref={input => {
this._inputElem.select(); this.input = input;
}} }}
readOnly="readonly" onFocus={() => {
value={address || ''} if (this.input) {
/> this.input.select();
{showCopyButton && ( }
<span className="header__item"> }}
<Link />
button="alt button--flat" <Button
icon="clipboard" alt
icon="Clipboard"
onClick={() => { onClick={() => {
clipboard.writeText(address); clipboard.writeText(address);
doShowSnackBar({ message: __('Address copied') }); doShowSnackBar({ message: __('Address copied') });
}} }}
/> />
</span> </React.Fragment>
)} )}
</div> />
); );
} }
} }

View file

@ -1,4 +1,3 @@
import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
selectPageTitle, selectPageTitle,
@ -10,7 +9,7 @@ import { doAlertError } from 'redux/actions/app';
import { doRecordScroll } from 'redux/actions/navigation'; import { doRecordScroll } from 'redux/actions/navigation';
import App from './view'; import App from './view';
const select = (state, props) => ({ const select = state => ({
pageTitle: selectPageTitle(state), pageTitle: selectPageTitle(state),
user: selectUser(state), user: selectUser(state),
currentStackIndex: selectHistoryIndex(state), currentStackIndex: selectHistoryIndex(state),

View file

@ -1,11 +1,12 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import Router from 'component/router/index'; import Router from 'component/router/index';
import Header from 'component/header';
import Theme from 'component/theme'; import Theme from 'component/theme';
import ModalRouter from 'modal/modalRouter'; import ModalRouter from 'modal/modalRouter';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import throttle from 'util/throttle'; import throttle from 'util/throttle';
import SideBar from 'component/sideBar';
import Header from 'component/header';
type Props = { type Props = {
alertError: (string | {}) => void, alertError: (string | {}) => void,
@ -33,7 +34,7 @@ class App extends React.PureComponent<Props> {
} }
componentDidMount() { componentDidMount() {
const mainContent = document.getElementById('main-content'); const mainContent = document.getElementById('content');
this.mainContent = mainContent; this.mainContent = mainContent;
if (this.mainContent) { if (this.mainContent) {
@ -82,9 +83,14 @@ class App extends React.PureComponent<Props> {
return ( return (
<div id="window"> <div id="window">
<Theme /> <Theme />
<Header /> <main className="page">
<Router /> <SideBar />
<ModalRouter /> <Header />
<div className="content" id="content">
<Router />
<ModalRouter />
</div>
</main>
</div> </div>
); );
} }

View file

@ -44,78 +44,6 @@ export class CurrencySymbol extends React.PureComponent {
} }
} }
export class CreditAmount extends React.PureComponent {
static propTypes = {
amount: PropTypes.number.isRequired,
precision: PropTypes.number,
isEstimate: PropTypes.bool,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
showFree: PropTypes.bool,
showFullPrice: PropTypes.bool,
showPlus: PropTypes.bool,
look: PropTypes.oneOf(['indicator', 'plain', 'fee']),
};
static defaultProps = {
precision: 2,
label: true,
showFree: false,
look: 'indicator',
showFullPrice: false,
showPlus: false,
};
render() {
const minimumRenderableAmount = Math.pow(10, -1 * this.props.precision);
const { amount, precision, showFullPrice } = this.props;
let formattedAmount;
const fullPrice = formatFullPrice(amount, 2);
if (showFullPrice) {
formattedAmount = fullPrice;
} else {
formattedAmount =
amount > 0 && amount < minimumRenderableAmount
? `<${minimumRenderableAmount}`
: formatCredits(amount, precision);
}
let amountText;
if (this.props.showFree && parseFloat(this.props.amount) === 0) {
amountText = __('free');
} else {
if (this.props.label) {
const label =
typeof this.props.label === 'string'
? this.props.label
: parseFloat(amount) == 1 ? __('credit') : __('credits');
amountText = `${formattedAmount} ${label}`;
} else {
amountText = formattedAmount;
}
if (this.props.showPlus && amount > 0) {
amountText = `+${amountText}`;
}
}
return (
<span className={`credit-amount credit-amount--${this.props.look}`} title={fullPrice}>
<span>{amountText}</span>
{this.props.isEstimate ? (
<span
className="credit-amount__estimate"
title={__('This is an estimate and does not include data fees')}
>
*
</span>
) : null}
</span>
);
}
}
export class Thumbnail extends React.PureComponent { export class Thumbnail extends React.PureComponent {
static propTypes = { static propTypes = {
src: PropTypes.string, src: PropTypes.string,

View file

@ -200,7 +200,7 @@ class CategoryList extends React.PureComponent<Props, State> {
<h3> <h3>
{categoryLink ? ( {categoryLink ? (
<Button <Button
className="button-text no-underline" noStyle
label={category} label={category}
navigate="/show" navigate="/show"
navigateParams={{ uri: categoryLink }} navigateParams={{ uri: categoryLink }}
@ -219,20 +219,20 @@ class CategoryList extends React.PureComponent<Props, State> {
/> />
)} )}
</div> </div>
<div> <div className="card-row__scroll-btns">
<Button <Button
inverse inverse
circle circle
disabled={!canScrollPrevious} disabled={!canScrollPrevious}
onClick={this.handleScrollPrevious} onClick={this.handleScrollPrevious}
icon="chevron-left" icon="ChevronLeft"
/> />
<Button <Button
inverse inverse
circle circle
disabled={!canScrollNext} disabled={!canScrollNext}
onClick={this.handleScrollNext} onClick={this.handleScrollNext}
icon="chevron-right" icon="ChevronRight"
/> />
</div> </div>
</div> </div>

View file

@ -0,0 +1,76 @@
// @flow
import React from 'react';
import classnames from 'classnames';
import { formatCredits, formatFullPrice } from 'util/formatCredits';
type Props = {
amount: number,
precision: number,
showFree: boolean,
showFullPrice: boolean,
showPlus: boolean,
isEstimate?: boolean,
large?: boolean,
};
class CreditAmount extends React.PureComponent<Props> {
static defaultProps = {
precision: 2,
showFree: false,
showFullPrice: false,
showPlus: false,
};
render() {
const { amount, precision, showFullPrice, showFree, showPlus, large, isEstimate } = this.props;
const minimumRenderableAmount = 10 ** (-1 * precision);
const fullPrice = formatFullPrice(amount, 2);
const isFree = parseFloat(amount) === 0;
let formattedAmount;
if (showFullPrice) {
formattedAmount = fullPrice;
} else {
formattedAmount =
amount > 0 && amount < minimumRenderableAmount
? `<${minimumRenderableAmount}`
: formatCredits(amount, precision);
}
let amountText;
if (showFree && isFree) {
amountText = __('FREE');
} else {
amountText = `${formattedAmount} ${__('LBC')}`;
if (showPlus && amount > 0) {
amountText = `+${amountText}`;
}
}
return (
<span
title={fullPrice}
className={classnames('credit-amount', {
'credit-amount--free': !large && isFree,
'credit-amount--cost': !large && !isFree,
'credit-amount--large': large,
})}
>
{amountText}
{isEstimate ? (
<span
className="credit-amount__estimate"
title={__('This is an estimate and does not include data fees')}
>
*
</span>
) : null}
</span>
);
}
}
export default CreditAmount;

View file

@ -0,0 +1,85 @@
// @flow
/* eslint-disable react/no-multi-comp */
import * as React from 'react';
import Button from 'component/link';
type FormRowProps = {
children: React.Node,
};
export const FormRow = (props: FormRowProps) => {
const { children } = props;
return <div className="form-row">{children}</div>;
};
type FormFieldProps = {
render: () => React.Node,
label?: string,
prefix?: string,
postfix?: string,
error?: string | boolean,
};
export class FormField extends React.PureComponent<FormFieldProps> {
render() {
const { render, label, prefix, postfix, error } = this.props;
return (
<div className="form-field">
{label && (
<label className="form-field__label">
{label}
</label>
)}
<div className="form-field__wrapper">
{prefix && <span className="form-field__prefix">{prefix}</span>}
{render()}
{postfix && <span className="form-field__postfix">{postfix}</span>}
</div>
{error && (
<div className="form-field__error">
{typeof error === 'string' ? error : __('There was an error')}
</div>
)}
</div>
);
}
}
type SubmitProps = {
label: string,
disabled: boolean,
};
export class Submit extends React.PureComponent<SubmitProps> {
static defaultProps = {
label: 'Submit',
};
render() {
const { label, disabled } = this.props;
return <Button type="submit" label={label} disabled={disabled} />;
}
}
type FormProps = {
children: React.Node,
onSubmit: any => any,
};
export class Form extends React.PureComponent<FormProps> {
render() {
const { children, onSubmit } = this.props;
return (
<form
className="form"
onSubmit={event => {
event.preventDefault();
onSubmit(event);
}}
>
{children}
</form>
);
}
}
/* eslint-enable react/no-multi-comp */

View file

@ -1,43 +1,22 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import classnames from 'classnames'; // import * as icons from 'constants/icons';
import * as icons from 'constants/icons'; import * as Icons from 'react-feather';
type Props = { type Props = {
icon: string, icon: string,
fixed?: boolean, size?: number,
padded?: boolean,
}; };
class Icon extends React.PureComponent<Props> { class IconComponent extends React.PureComponent<Props> {
getIconTitle() { // TODO: Move all icons to constants and add titles for all
const { icon } = this.props; // Add some some sort of hover flyout with the title?
switch (icon) {
case icons.FEATURED:
return __('Watch this and earn rewards.');
case icons.LOCAL:
return __('You have a copy of this file.');
default:
return '';
}
}
render() { render() {
const { icon, fixed, padded } = this.props; const { icon, size = 14 } = this.props;
const iconClassName = icon.startsWith('icon-') ? icon : `icon-${icon}`; const Icon = Icons[icon];
const title = this.getIconTitle(); return Icon ? <Icon size={size} className="icon" /> : null;
const spanClassName = classnames(
{
'icon--fixed-width': fixed,
'icon--padded': padded,
},
iconClassName
);
return <span className={spanClassName} title={title} />;
} }
} }
export default Icon; export default IconComponent;

View file

@ -46,7 +46,7 @@ class ToolTip extends React.PureComponent<Props, State> {
<span className="tooltip"> <span className="tooltip">
<Button fakeLink className="help tooltip__link" onClick={this.handleClick}> <Button fakeLink className="help tooltip__link" onClick={this.handleClick}>
{label} {label}
{showTooltip && <Icon icon="times" fixed />} {showTooltip && <Icon icon="X" />}
</Button> </Button>
<div className={classnames('tooltip__body', { hidden: !showTooltip })}>{body}</div> <div className={classnames('tooltip__body', { hidden: !showTooltip })}>{body}</div>
</span> </span>

View file

@ -78,6 +78,9 @@ class FileCard extends React.PureComponent<Props> {
})} })}
> >
<CardMedia thumbnail={thumbnail} /> <CardMedia thumbnail={thumbnail} />
<div className="card-media__internal-links">
<FilePrice uri={uri} />
</div>
<div className="card__title-identity"> <div className="card__title-identity">
<div className="card__title--small"> <div className="card__title--small">
@ -87,8 +90,8 @@ class FileCard extends React.PureComponent<Props> {
<div className="card__subtitle"> <div className="card__subtitle">
<UriIndicator uri={uri} link /> <UriIndicator uri={uri} link />
<div className="card--file-subtitle"> <div className="card--file-subtitle">
<FilePrice uri={uri} /> {isRewardContent && <Icon icon={icons.FEATURED} padded />} {isRewardContent && <Icon icon={icons.FEATURED} />}
{fileInfo && <Icon icon={icons.LOCAL} padded />} {fileInfo && <Icon icon={icons.LOCAL} />}
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,35 +1,48 @@
// @flow
import React from 'react'; import React from 'react';
import { CreditAmount } from 'component/common'; import CreditAmount from 'component/common/credit-amount';
type Props = {
showFullPrice: boolean,
costInfo: ?{ includesData: boolean, cost: number },
fetchCostInfo: string => void,
uri: string,
fetching: boolean,
claim: ?{},
};
class FilePrice extends React.PureComponent<Props> {
static defaultProps = {
showFullPrice: false,
};
class FilePrice extends React.PureComponent {
componentWillMount() { componentWillMount() {
this.fetchCost(this.props); this.fetchCost(this.props);
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps: Props) {
this.fetchCost(nextProps); this.fetchCost(nextProps);
} }
fetchCost(props) { fetchCost = (props: Props) => {
const { costInfo, fetchCostInfo, uri, fetching, claim } = props; const { costInfo, fetchCostInfo, uri, fetching, claim } = props;
if (costInfo === undefined && !fetching && claim) { if (costInfo === undefined && !fetching && claim) {
fetchCostInfo(uri); fetchCostInfo(uri);
} }
} };
render() { render() {
const { costInfo, look = 'indicator', showFullPrice = false } = this.props; const { costInfo, showFullPrice } = this.props;
const isEstimate = costInfo ? !costInfo.includesData : null; const isEstimate = costInfo ? !costInfo.includesData : false;
if (!costInfo) { if (!costInfo) {
return <span className={`credit-amount credit-amount--${look}`}>???</span>; return <span className="credit-amount">???</span>;
} }
return ( return (
<CreditAmount <CreditAmount
label={false}
amount={costInfo.cost} amount={costInfo.cost}
isEstimate={isEstimate} isEstimate={isEstimate}
showFree showFree

View file

@ -1,186 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import FormField from 'component/formField';
import Icon from 'component/common/icon';
let formFieldCounter = 0;
export const formFieldNestedLabelTypes = ['radio', 'checkbox'];
export function formFieldId() {
return `form-field-${++formFieldCounter}`;
}
export class Form extends React.PureComponent {
static propTypes = {
onSubmit: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
}
handleSubmit(event) {
event.preventDefault();
this.props.onSubmit();
}
render() {
return <form onSubmit={event => this.handleSubmit(event)}>{this.props.children}</form>;
}
}
export class FormRow extends React.PureComponent {
static propTypes = {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
errorMessage: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
// helper: PropTypes.html,
};
static defaultProps = {
isFocus: false,
};
constructor(props) {
super(props);
this._field = null;
this._fieldRequiredText = __('This field is required');
this.state = this.getStateFromProps(props);
}
componentWillReceiveProps(nextProps) {
this.setState(this.getStateFromProps(nextProps));
}
getStateFromProps(props) {
return {
isError: !!props.errorMessage,
errorMessage:
typeof props.errorMessage === 'string'
? props.errorMessage
: props.errorMessage instanceof Error ? props.errorMessage.toString() : '',
};
}
showError(text) {
this.setState({
isError: true,
errorMessage: text,
});
}
showRequiredError() {
this.showError(this._fieldRequiredText);
}
clearError(text) {
this.setState({
isError: false,
errorMessage: '',
});
}
getValue() {
return this._field.getValue();
}
getSelectedElement() {
return this._field.getSelectedElement();
}
getOptions() {
return this._field.getOptions();
}
focus() {
this._field.focus();
}
onFocus() {
this.setState({ isFocus: true });
}
onBlur() {
this.setState({ isFocus: false });
}
render() {
const fieldProps = Object.assign({}, this.props),
elementId = formFieldId(),
renderLabelInFormField = formFieldNestedLabelTypes.includes(this.props.type);
if (!renderLabelInFormField) {
delete fieldProps.label;
}
delete fieldProps.helper;
delete fieldProps.errorMessage;
delete fieldProps.isFocus;
return (
<div className={`form-row${this.state.isFocus ? ' form-row--focus' : ''}`}>
{this.props.label && !renderLabelInFormField ? (
<div
className={`form-row__label-row ${
this.props.labelPrefix ? 'form-row__label-row--prefix' : ''
}`}
>
<label
htmlFor={elementId}
className={`form-field__label ${
this.state.isError ? 'form-field__label--error' : ' '
}`}
>
{this.props.label}
</label>
</div>
) : (
''
)}
<FormField
ref={ref => {
this._field = ref ? ref.getWrappedInstance() : null;
}}
hasError={this.state.isError}
onFocus={this.onFocus.bind(this)}
onBlur={this.onBlur.bind(this)}
{...fieldProps}
/>
{!this.state.isError && this.props.helper ? (
<div className="form-field__helper">{this.props.helper}</div>
) : (
''
)}
{this.state.isError ? (
<div className="form-field__error">{this.state.errorMessage}</div>
) : (
''
)}
</div>
);
}
}
export const Submit = props => {
const { title, label, icon, disabled } = props;
const className = `${'button-block' +
' button-primary' +
' button-set-item' +
' button--submit'}${disabled ? ' disabled' : ''}`;
const content = (
<span className="button__content">
{'icon' in props ? <Icon icon={icon} fixed /> : null}
{label ? <span className="button-label">{label}</span> : null}
</span>
);
return (
<button type="submit" className={className} title={title}>
{content}
</button>
);
};

View file

@ -1,8 +1,10 @@
// This file is going to die
/* eslint-disable */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import FileSelector from 'component/file-selector.js'; import FileSelector from 'component/file-selector.js';
import SimpleMDE from 'react-simplemde-editor'; import SimpleMDE from 'react-simplemde-editor';
import { formFieldNestedLabelTypes, formFieldId } from '../form'; import { formFieldNestedLabelTypes, formFieldId } from 'component/common/form';
import style from 'react-simplemde-editor/dist/simplemde.min.css'; import style from 'react-simplemde-editor/dist/simplemde.min.css';
const formFieldFileSelectorTypes = ['file', 'directory']; const formFieldFileSelectorTypes = ['file', 'directory'];
@ -195,3 +197,4 @@ class FormField extends React.PureComponent {
} }
export default FormField; export default FormField;
/* eslint-enable */

View file

@ -1,25 +1,17 @@
import React from 'react';
import { formatCredits } from 'util/formatCredits';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectIsBackDisabled, selectIsForwardDisabled } from 'redux/selectors/navigation'; import { doNavigate } from 'redux/actions/navigation';
import { selectBalance } from 'redux/selectors/wallet';
import { doNavigate, doHistoryBack, doHistoryForward } from 'redux/actions/navigation';
import Header from './view';
import { selectIsUpgradeAvailable } from 'redux/selectors/app'; import { selectIsUpgradeAvailable } from 'redux/selectors/app';
import { doDownloadUpgrade } from 'redux/actions/app'; import { formatCredits } from 'util/formatCredits';
import { selectBalance } from 'redux/selectors/wallet';
import Header from './view';
const select = state => ({ const select = state => ({
isBackDisabled: selectIsBackDisabled(state),
isForwardDisabled: selectIsForwardDisabled(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state), isUpgradeAvailable: selectIsUpgradeAvailable(state),
balance: formatCredits(selectBalance(state) || 0, 2), balance: formatCredits(selectBalance(state) || 0, 2),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)), navigate: path => dispatch(doNavigate(path)),
back: () => dispatch(doHistoryBack()),
forward: () => dispatch(doHistoryForward()),
downloadUpgrade: () => dispatch(doDownloadUpgrade()),
}); });
export default connect(select, perform)(Header); export default connect(select, perform)(Header);

View file

@ -5,78 +5,34 @@ import WunderBar from 'component/wunderbar';
type Props = { type Props = {
balance: string, balance: string,
back: any => void,
forward: any => void,
isBackDisabled: boolean,
isForwardDisabled: boolean,
isUpgradeAvailable: boolean,
navigate: any => void, navigate: any => void,
downloadUpgrade: any => void, downloadUpgrade: any => void,
isUpgradeAvailable: boolean,
}; };
export const Header = (props: Props) => { const Header = (props: Props) => {
const { const { balance, isUpgradeAvailable, navigate, downloadUpgrade } = props;
balance,
back,
forward,
isBackDisabled,
isForwardDisabled,
isUpgradeAvailable,
navigate,
downloadUpgrade,
} = props;
return ( return (
<header id="header"> <header className="header">
<div className="header__actions-left">
<Button
alt
circle
onClick={back}
disabled={isBackDisabled}
icon="arrow-left"
description={__('Navigate back')}
/>
<Button
alt
circle
onClick={forward}
disabled={isForwardDisabled}
icon="arrow-right"
description={__('Navigate forward')}
/>
<Button alt onClick={() => navigate('/discover')} icon="home" description={__('Home')} />
</div>
<WunderBar /> <WunderBar />
<div className="header__actions-right"> <div className="header__actions-right">
<Button <Button
inverse inverse
onClick={() => navigate('/wallet')} onClick={() => navigate('/wallet')}
icon="user" icon="User"
label={isUpgradeAvailable ? `${balance} LBC` : `You have ${balance} LBC`} label={isUpgradeAvailable ? `${balance} LBC` : `You have ${balance} LBC`}
description={__('Your wallet')} description={__('Your wallet')}
/> />
<Button <Button
onClick={() => navigate('/publish')} onClick={() => navigate('/publish')}
icon="cloud-upload" icon="UploadCloud"
label={isUpgradeAvailable ? '' : __('Publish')} label={isUpgradeAvailable ? '' : __('Publish')}
description={__('Publish content')} description={__('Publish content')}
/> />
<Button
alt
onClick={() => navigate('/settings')}
icon="gear"
description={__('Settings')}
/>
<Button alt onClick={() => navigate('/help')} icon="question" description={__('Help')} />
{isUpgradeAvailable && ( {isUpgradeAvailable && (
<Button onClick={() => downloadUpgrade()} icon="arrow-up" label={__('Upgrade App')} /> <Button onClick={() => downloadUpgrade()} icon="Download" label={__('Upgrade App')} />
)} )}
</div> </div>
</header> </header>

View file

@ -1,6 +1,9 @@
// I'll come back to this
/* eslint-disable */
import React from 'react'; import React from 'react';
import { BusyMessage, CreditAmount } from 'component/common'; import { BusyMessage } from 'component/common';
import { Form, FormRow, Submit } from 'component/form.js'; import CreditAmount from 'component/common/credit-amount';
// import { Form, FormField } from 'component/common/form';
class FormInviteNew extends React.PureComponent { class FormInviteNew extends React.PureComponent {
constructor(props) { constructor(props) {
@ -24,25 +27,25 @@ class FormInviteNew extends React.PureComponent {
render() { render() {
const { errorMessage, isPending } = this.props; const { errorMessage, isPending } = this.props;
return null;
return ( // return (
<Form onSubmit={this.handleSubmit.bind(this)}> // <Form onSubmit={this.handleSubmit.bind(this)}>
<FormRow // <FormRow
type="text" // type="text"
label="Email" // label="Email"
placeholder="youremail@example.org" // placeholder="youremail@example.org"
name="email" // name="email"
value={this.state.email} // value={this.state.email}
errorMessage={errorMessage} // errorMessage={errorMessage}
onChange={event => { // onChange={event => {
this.handleEmailChanged(event); // this.handleEmailChanged(event);
}} // }}
/> // />
<div className="form-row-submit"> // <div className="form-row-submit">
<Submit label={__('Send Invite')} disabled={isPending} /> // <Submit label={__('Send Invite')} disabled={isPending} />
</div> // </div>
</Form> // </Form>
); // );
} }
} }
@ -80,3 +83,4 @@ class InviteNew extends React.PureComponent {
} }
export default InviteNew; export default InviteNew;
/* eslint-enable */

View file

@ -22,6 +22,8 @@ type Props = {
alt: ?boolean, alt: ?boolean,
flat: ?boolean, flat: ?boolean,
fakeLink: ?boolean, fakeLink: ?boolean,
noStyle: ?boolean,
noUnderline: ?boolean,
description: ?string, description: ?string,
}; };
@ -45,20 +47,25 @@ const Button = (props: Props) => {
flat, flat,
fakeLink, fakeLink,
description, description,
noStyle,
noUnderline,
...otherProps ...otherProps
} = props; } = props;
const combinedClassName = classnames( const combinedClassName = classnames(
{ 'btn',
btn: !fakeLink, noStyle
'btn--link': fakeLink, ? 'btn--no-style'
'btn--primary': !fakeLink && !alt, : {
'btn--alt': alt, 'btn--link': fakeLink,
'btn--inverse': inverse, 'btn--primary': !alt && !fakeLink,
'btn--disabled': disabled, 'btn--alt': alt,
'btn--circle': circle, 'btn--inverse': inverse,
'btn--flat': flat, 'btn--disabled': disabled,
}, 'btn--circle': circle,
'btn--flat': flat,
'btn--no-underline': fakeLink && noUnderline,
},
className className
); );
@ -72,10 +79,10 @@ const Button = (props: Props) => {
const content = ( const content = (
<React.Fragment> <React.Fragment>
{icon && <Icon icon={icon} fixed />} {icon && <Icon icon={icon} />}
{label && <span className="btn__label">{label}</span>} {label && <span className="btn__label">{label}</span>}
{children && children} {children && children}
{iconRight && <Icon icon={iconRight} fixed />} {iconRight && <Icon icon={iconRight} />}
</React.Fragment> </React.Fragment>
); );

View file

@ -1,111 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import Icon from 'component/common/icon';
import Link from 'component/link';
export class DropDownMenuItem extends React.PureComponent {
static propTypes = {
href: PropTypes.string,
label: PropTypes.string,
icon: PropTypes.string,
onClick: PropTypes.func,
};
static defaultProps = {
iconPosition: 'left',
};
render() {
const icon = this.props.icon ? <Icon icon={this.props.icon} fixed /> : null;
return (
<a
className="menu__menu-item"
onClick={this.props.onClick}
href={this.props.href || 'javascript:'}
label={this.props.label}
>
{this.props.iconPosition == 'left' ? icon : null}
{this.props.label}
{this.props.iconPosition == 'left' ? null : icon}
</a>
);
}
}
export class DropDownMenu extends React.PureComponent {
constructor(props) {
super(props);
this._isWindowClickBound = false;
this._menuDiv = null;
this.state = {
menuOpen: false,
};
}
componentWillUnmount() {
if (this._isWindowClickBound) {
window.removeEventListener('click', this.handleWindowClick, false);
}
}
handleMenuIconClick(e) {
this.setState({
menuOpen: !this.state.menuOpen,
});
if (!this.state.menuOpen && !this._isWindowClickBound) {
this._isWindowClickBound = true;
window.addEventListener('click', this.handleWindowClick, false);
e.stopPropagation();
}
return false;
}
handleMenuClick(e) {
// Event bubbles up to the menu after a link is clicked
this.setState({
menuOpen: false,
});
}
/* this will force "this" to always be the class, even when passed to an event listener */
handleWindowClick = e => {
if (this.state.menuOpen && (!this._menuDiv || !this._menuDiv.contains(e.target))) {
this.setState({
menuOpen: false,
});
}
};
render() {
if (!this.state.menuOpen && this._isWindowClickBound) {
this._isWindowClickBound = false;
window.removeEventListener('click', this.handleWindowClick, false);
}
return (
<div className="menu-container">
<Link
ref={span => (this._menuButton = span)}
button="text"
icon="icon-ellipsis-v"
onClick={event => {
this.handleMenuIconClick(event);
}}
/>
{this.state.menuOpen ? (
<div
ref={div => (this._menuDiv = div)}
className="menu"
onClick={event => {
this.handleMenuClick(event);
}}
>
{this.props.children}
</div>
) : null}
</div>
);
}
}

View file

@ -1,9 +1,31 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectPageTitle } from 'redux/selectors/navigation'; import {
selectPageTitle,
selectIsBackDisabled,
selectIsForwardDisabled,
selectNavLinks,
} from 'redux/selectors/navigation';
import { doNavigate, doHistoryBack, doHistoryForward } from 'redux/actions/navigation';
import { doDownloadUpgrade } from 'redux/actions/app';
import { selectIsUpgradeAvailable } from 'redux/selectors/app';
import { formatCredits } from 'util/formatCredits';
import { selectBalance } from 'redux/selectors/wallet';
import Page from './view'; import Page from './view';
const select = state => ({ const select = state => ({
title: selectPageTitle(state), pageTitle: selectPageTitle(state),
navLinks: selectNavLinks(state),
isBackDisabled: selectIsBackDisabled(state),
isForwardDisabled: selectIsForwardDisabled(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state),
balance: formatCredits(selectBalance(state) || 0, 2),
}); });
export default connect(select, null)(Page); const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
back: () => dispatch(doHistoryBack()),
forward: () => dispatch(doHistoryForward()),
downloadUpgrade: () => dispatch(doDownloadUpgrade()),
});
export default connect(select, perform)(Page);

View file

@ -1,24 +1,23 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { BusyMessage } from 'component/common';
type Props = { type Props = {
children: React.Node, children: React.Node,
title: ?string, pageTitle: ?string,
noPadding: ?boolean, noPadding: ?boolean,
isLoading: ?boolean,
}; };
const Page = (props: Props) => { const Page = (props: Props) => {
const { children, title, noPadding, isLoading } = props; const { pageTitle, children, noPadding } = props;
return ( return (
<main id="main-content"> <main className={classnames('main', { 'main--no-padding': noPadding })}>
<div className="page__header"> {pageTitle && (
{title && <h1 className="page__title">{title}</h1>} <div className="page__header">
{isLoading && <BusyMessage message={__('Fetching content')} />} {pageTitle && <h1 className="page__title">{pageTitle}</h1>}
</div> </div>
<div className={classnames('main', { 'main--no-padding': noPadding })}>{children}</div> )}
{children}
</main> </main>
); );
}; };

View file

@ -1,6 +1,8 @@
// I'll come back to this
/* eslint-disable */
import React from 'react'; import React from 'react';
import { isNameValid } from 'lbryURI'; import { isNameValid } from 'lbryURI';
import { FormRow } from 'component/form.js'; import { FormRow } from 'component/common/form';
import { BusyMessage } from 'component/common'; import { BusyMessage } from 'component/common';
import Link from 'component/link'; import Link from 'component/link';
@ -167,3 +169,4 @@ class ChannelSection extends React.PureComponent {
} }
export default ChannelSection; export default ChannelSection;
/* eslint-enable */

View file

@ -1,8 +1,10 @@
// I'll come back to this
/* eslint-disable */
import React from 'react'; import React from 'react';
import lbry from 'lbry'; import lbry from 'lbry';
import { isNameValid, buildURI, regexInvalidURI } from 'lbryURI'; import { isNameValid, buildURI, regexInvalidURI } from 'lbryURI';
import FormField from 'component/formField'; import FormField from 'component/formField';
import { Form, FormRow, Submit } from 'component/form.js'; import { Form, FormRow } from 'component/common/form';
import Link from 'component/link'; import Link from 'component/link';
import FormFieldPrice from 'component/formFieldPrice'; import FormFieldPrice from 'component/formFieldPrice';
import Modal from 'modal/modal'; import Modal from 'modal/modal';
@ -832,7 +834,8 @@ class PublishForm extends React.PureComponent {
</section> </section>
<div className="card-series-submit"> <div className="card-series-submit">
<Submit <Link
type="submit"
label={!this.state.submitting ? __('Publish') : __('Publishing...')} label={!this.state.submitting ? __('Publish') : __('Publishing...')}
disabled={ disabled={
this.props.balance <= 0 || this.props.balance <= 0 ||
@ -878,3 +881,4 @@ class PublishForm extends React.PureComponent {
} }
export default PublishForm; export default PublishForm;
/* eslint-enable */

View file

@ -1,7 +1,20 @@
// @flow
import React from 'react'; import React from 'react';
import LinkTransaction from 'component/linkTransaction'; import LinkTransaction from 'component/linkTransaction';
const RewardListClaimed = props => { type Reward = {
id: string,
reward_title: string,
reward_amount: number,
transaction_id: string,
created_at: string,
};
type Props = {
rewards: Array<Reward>,
};
const RewardListClaimed = (props: Props) => {
const { rewards } = props; const { rewards } = props;
if (!rewards || !rewards.length) { if (!rewards || !rewards.length) {
@ -9,7 +22,7 @@ const RewardListClaimed = props => {
} }
return ( return (
<section className="card"> <section className="card card--section">
<div className="card__title-identity"> <div className="card__title-identity">
<h3>Claimed Rewards</h3> <h3>Claimed Rewards</h3>
</div> </div>

View file

@ -1,7 +1,7 @@
// @flow // @flow
import React from 'react'; import * as React from 'react';
import Link from 'component/link'; import Button from 'component/link';
import { CreditAmount } from 'component/common'; import CreditAmount from 'component/common/credit-amount';
type Props = { type Props = {
unclaimedRewardAmount: number, unclaimedRewardAmount: number,
@ -9,24 +9,25 @@ type Props = {
const RewardSummary = (props: Props) => { const RewardSummary = (props: Props) => {
const { unclaimedRewardAmount } = props; const { unclaimedRewardAmount } = props;
const hasRewards = unclaimedRewardAmount > 0;
return ( return (
<section className="card"> <section className="card card--section">
<div className="card__title-primary"> <h2>{__('Rewards')}</h2>
<h3>{__('Rewards')}</h3> <p className="card__subtitle">
</div> {hasRewards ? (
<div className="card__content"> <React.Fragment>
{unclaimedRewardAmount > 0 ? (
<p>
{__('You have')} <CreditAmount amount={unclaimedRewardAmount} precision={8} />{' '} {__('You have')} <CreditAmount amount={unclaimedRewardAmount} precision={8} />{' '}
{__('in unclaimed rewards')}. {__('in unclaimed rewards')}.
</p> </React.Fragment>
) : ( ) : (
<p>{__('There are no rewards available at this time, please check back later')}.</p> <React.Fragment>
{__('There are no rewards available at this time, please check back later')}.
</React.Fragment>
)} )}
</div> </p>
<div className="card__actions"> <div className="card__actions">
<Link button="primary" navigate="/rewards" label={__('Claim Rewards')} /> <Button navigate="/rewards" label={hasRewards ? __('Claim Rewards') : __('View Rewards')} />
</div> </div>
</section> </section>
); );

View file

@ -1,16 +1,30 @@
// @flow
import React from 'react'; import React from 'react';
import { CreditAmount, Icon } from 'component/common'; import CreditAmount from 'component/common/credit-amount';
import Icon from 'component/common/icon';
import RewardLink from 'component/rewardLink'; import RewardLink from 'component/rewardLink';
import Link from 'component/link'; import Button from 'component/link';
import rewards from 'rewards'; import rewards from 'rewards';
const RewardTile = props => { type Props = {
reward: {
id: string,
reward_title: string,
reward_amount: number,
transaction_id: string,
created_at: string,
reward_description: string,
reward_type: string,
},
};
const RewardTile = (props: Props) => {
const { reward } = props; const { reward } = props;
const claimed = !!reward.transaction_id; const claimed = !!reward.transaction_id;
return ( return (
<section className="card"> <section className="card card--section">
<div className="card__inner"> <div className="card__inner">
<div className="card__title-primary"> <div className="card__title-primary">
<CreditAmount amount={reward.reward_amount} /> <CreditAmount amount={reward.reward_amount} />
@ -18,8 +32,8 @@ const RewardTile = props => {
</div> </div>
<div className="card__content">{reward.reward_description}</div> <div className="card__content">{reward.reward_description}</div>
<div className="card__actions "> <div className="card__actions ">
{reward.reward_type == rewards.TYPE_REFERRAL && ( {reward.reward_type === rewards.TYPE_REFERRAL && (
<Link button="alt" navigate="/invite" label={__('Go To Invites')} /> <Button alt navigate="/invite" label={__('Go To Invites')} />
)} )}
{reward.reward_type !== rewards.TYPE_REFERRAL && {reward.reward_type !== rewards.TYPE_REFERRAL &&
(claimed ? ( (claimed ? (

View file

@ -1,7 +1,7 @@
// @flow
import React from 'react'; import React from 'react';
import Link from 'component/link';
import { getExampleAddress } from 'util/shape_shift'; import { getExampleAddress } from 'util/shape_shift';
import { Submit, FormRow } from 'component/form'; import { FormField, Submit } from 'component/common/form';
import type { ShapeShiftFormValues, Dispatch } from 'redux/actions/shape_shift'; import type { ShapeShiftFormValues, Dispatch } from 'redux/actions/shape_shift';
import ShiftMarketInfo from './market_info'; import ShiftMarketInfo from './market_info';
@ -12,7 +12,7 @@ type ShapeShiftFormErrors = {
type Props = { type Props = {
values: ShapeShiftFormValues, values: ShapeShiftFormValues,
errors: ShapeShiftFormErrors, errors: ShapeShiftFormErrors,
touched: boolean, touched: { returnAddress: boolean },
handleChange: Event => any, handleChange: Event => any,
handleBlur: Event => any, handleBlur: Event => any,
handleSubmit: Event => any, handleSubmit: Event => any,
@ -21,7 +21,6 @@ type Props = {
originCoin: string, originCoin: string,
updating: boolean, updating: boolean,
getCoinStats: Dispatch, getCoinStats: Dispatch,
receiveAddress: string,
originCoinDepositFee: number, originCoinDepositFee: number,
originCoinDepositMin: string, originCoinDepositMin: string,
originCoinDepositMax: number, originCoinDepositMax: number,
@ -41,7 +40,6 @@ export default (props: Props) => {
originCoin, originCoin,
updating, updating,
getCoinStats, getCoinStats,
receiveAddress,
originCoinDepositMax, originCoinDepositMax,
originCoinDepositMin, originCoinDepositMin,
originCoinDepositFee, originCoinDepositFee,
@ -49,23 +47,27 @@ export default (props: Props) => {
} = props; } = props;
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-field"> <FormField
<span>{__('Exchange')} </span> prefix={__('Exchange')}
<select postfix={__('for LBC')}
className="form-field__input form-field__input-select" render={() => (
name="originCoin" <select
onChange={e => { className="form-field__input form-field__input-select"
getCoinStats(e.target.value); name="originCoin"
handleChange(e); onChange={e => {
}} getCoinStats(e.target.value);
> handleChange(e);
{shiftSupportedCoins.map(coin => ( }}
<option key={coin} value={coin}> >
{coin} {shiftSupportedCoins.map(coin => (
</option> <option key={coin} value={coin}>
))} {coin}
</select> </option>
<span> {__('for LBC')}</span> ))}
</select>
)}
/>
<div>
<div className="shapeshift__tx-info"> <div className="shapeshift__tx-info">
{!updating && {!updating &&
originCoinDepositMax && ( originCoinDepositMax && (
@ -80,16 +82,19 @@ export default (props: Props) => {
</div> </div>
</div> </div>
<FormRow <FormField
type="text"
name="returnAddress"
placeholder={getExampleAddress(originCoin)}
label={__('Return address')} label={__('Return address')}
onChange={handleChange} error={touched.returnAddress && !!errors.returnAddress && errors.returnAddress}
onBlur={handleBlur} render={() => (
value={values.returnAddress} <input
errorMessage={errors.returnAddress} type="text"
hasError={touched.returnAddress && !!errors.returnAddress} name="returnAddress"
placeholder={getExampleAddress(originCoin)}
onChange={handleChange}
onBlur={handleBlur}
value={values.returnAddress}
/>
)}
/> />
<span className="help"> <span className="help">
<span> <span>

View file

@ -1,18 +1,12 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import { shell } from 'electron';
import { Formik } from 'formik'; import { Formik } from 'formik';
import classnames from 'classnames';
import * as statuses from 'constants/shape_shift';
import { validateShapeShiftForm } from 'util/shape_shift'; import { validateShapeShiftForm } from 'util/shape_shift';
import Link from 'component/link'; import Link from 'component/link';
import Spinner from 'component/common/spinner';
import { BusyMessage } from 'component/common';
import ShapeShiftForm from './internal/form';
import ActiveShapeShift from './internal/active-shift';
import type { ShapeShiftState } from 'redux/reducers/shape_shift'; import type { ShapeShiftState } from 'redux/reducers/shape_shift';
import type { Dispatch, ShapeShiftFormValues } from 'redux/actions/shape_shift'; import type { Dispatch, ShapeShiftFormValues } from 'redux/actions/shape_shift';
import ShapeShiftForm from './internal/form';
import ActiveShapeShift from './internal/active-shift';
type Props = { type Props = {
shapeShift: ShapeShiftState, shapeShift: ShapeShiftState,
@ -72,28 +66,17 @@ class ShapeShift extends React.PureComponent<Props> {
}; };
return ( return (
// add the "shapeshift__intital-wrapper class so we can avoid content jumping once everything loads" <section className="card card--section">
// it just gives the section a min-height equal to the height of the content when the form is rendered <h2>{__('Convert Crypto to LBC')}</h2>
// if the markup below changes for the initial render (form.jsx) there will be content jumping <p className="card__subtitle">
// the styling in shapeshift.scss will need to be updated to the correct min-height {__('Powered by ShapeShift. Read our FAQ')}{' '}
<section <Link fakeLink label={__('here')} href="https://lbry.io/faq/shapeshift" />.
className={classnames('card shapeshift__wrapper', { {hasActiveShift &&
'shapeshift__initial-wrapper': loading, shiftState !== 'complete' && <span>{__('This will update automatically.')}</span>}
})} </p>
>
<div className="card__title-primary">
<h3>{__('Convert Crypto to LBC')}</h3>
<p className="help">
{__('Powered by ShapeShift. Read our FAQ')}{' '}
<Link href="https://lbry.io/faq/shapeshift">{__('here')}</Link>.
{hasActiveShift &&
shiftState !== 'complete' && <span>{__('This will update automatically.')}</span>}
</p>
</div>
<div className="card__content shapeshift__content"> <div className="card__content shapeshift__content">
{error && <div className="form-field__error">{error}</div>} {error && <div className="form-field__error">{error}</div>}
{loading && <Spinner dark />}
{!loading && {!loading &&
!hasActiveShift && !hasActiveShift &&
!!shiftSupportedCoins.length && ( !!shiftSupportedCoins.length && (
@ -113,7 +96,6 @@ class ShapeShift extends React.PureComponent<Props> {
originCoinDepositMin={originCoinDepositMin} originCoinDepositMin={originCoinDepositMin}
originCoinDepositFee={originCoinDepositFee} originCoinDepositFee={originCoinDepositFee}
shapeShiftRate={shapeShiftRate} shapeShiftRate={shapeShiftRate}
updating={updating}
/> />
)} )}
/> />
@ -124,7 +106,6 @@ class ShapeShift extends React.PureComponent<Props> {
shiftCoinType={shiftCoinType} shiftCoinType={shiftCoinType}
shiftReturnAddress={shiftReturnAddress} shiftReturnAddress={shiftReturnAddress}
shiftDepositAddress={shiftDepositAddress} shiftDepositAddress={shiftDepositAddress}
originCoinDepositMax={originCoinDepositMax}
shiftOrderId={shiftOrderId} shiftOrderId={shiftOrderId}
shiftState={shiftState} shiftState={shiftState}
clearShapeShift={clearShapeShift} clearShapeShift={clearShapeShift}

View file

@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import { doNavigate, doHistoryBack, doHistoryForward } from 'redux/actions/navigation';
import {
selectIsBackDisabled,
selectIsForwardDisabled,
selectIsHome,
selectNavLinks,
} from 'redux/selectors/navigation';
import SideBar from './view';
const select = state => ({
navLinks: selectNavLinks(state),
isBackDisabled: selectIsBackDisabled(state),
isForwardDisabled: selectIsForwardDisabled(state),
isHome: selectIsHome(state),
});
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
back: () => dispatch(doHistoryBack()),
forward: () => dispatch(doHistoryForward()),
});
export default connect(select, perform)(SideBar);

View file

@ -0,0 +1,101 @@
// @flow
import React from 'react';
import Button from 'component/link';
import classnames from 'classnames';
type SideBarLink = {
label: string,
path: string,
active: boolean,
icon: ?string,
subLinks: Array<SideBarLink>,
};
type Props = {
navigate: any => void,
back: any => void,
forward: any => void,
isBackDisabled: boolean,
isForwardDisabled: boolean,
isHome: boolean,
navLinks: {
primary: Array<SideBarLink>,
secondary: Array<SideBarLink>,
},
};
const SideBar = (props: Props) => {
const { navigate, back, forward, isBackDisabled, isForwardDisabled, isHome, navLinks } = props;
return (
<nav className="nav">
<div className="nav__actions-top">
<Button
alt
icon="Home"
description={__('Home')}
onClick={() => navigate('/discover')}
disabled={isHome}
/>
<div className="nav__actions-history">
<Button
alt
icon="ArrowLeft"
description={__('Navigate back')}
onClick={back}
disabled={isBackDisabled}
/>
<Button
alt
icon="ArrowRight"
description={__('Navigate forward')}
onClick={forward}
disabled={isForwardDisabled}
/>
</div>
</div>
<div className="nav__links">
<ul className="nav__primary">
{navLinks.primary.map(({ label, path, active, icon }) => (
<li
key={path}
className={classnames('nav__link nav__primary-link', { 'nav__link--active': active })}
>
<Button noStyle navigate={path} label={label} icon={icon} />
</li>
))}
</ul>
<hr />
<ul className="nav__secondary">
{navLinks.secondary.map(({ label, path, active, icon, subLinks = [] }) => (
<li
key={path}
className={classnames('nav__link nav__secondary-link', {
'nav__link--active': active && !subLinks.length,
})}
>
<Button noStyle navigate={path} label={label} icon={icon} />
{!!subLinks.length &&
active && (
<ul className="nav__sub">
{subLinks.map(({ label: subLabel, path: subPath, active: subLinkActive }) => (
<li
key={subPath}
className={classnames('nav__link nav__sub-link', {
'nav__link--active': subLinkActive,
})}
>
<Button noStyle navigate={subPath} label={subLabel} />
</li>
))}
</ul>
)}
</li>
))}
</ul>
</div>
</nav>
);
};
export default SideBar;

View file

@ -1,16 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { selectCurrentPage, selectHeaderLinks } from 'redux/selectors/navigation';
import { doNavigate } from 'redux/actions/navigation';
import SubHeader from './view';
const select = (state, props) => ({
currentPage: selectCurrentPage(state),
subLinks: selectHeaderLinks(state),
});
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
});
export default connect(select, perform)(SubHeader);

View file

@ -1,34 +0,0 @@
import React from 'react';
import Link from 'component/link';
import classnames from 'classnames';
const SubHeader = props => {
const { subLinks, currentPage, navigate, fullWidth, smallMargin } = props;
const links = [];
for (const link of Object.keys(subLinks)) {
links.push(
<Link
onClick={event => navigate(`/${link}`, event)}
key={link}
className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected'}
>
{subLinks[link]}
</Link>
);
}
return (
<nav
className={classnames('sub-header', {
'sub-header--full-width': fullWidth,
'sub-header--small-margin': smallMargin,
})}
>
{links}
</nav>
);
};
export default SubHeader;

View file

@ -1,6 +1,8 @@
// I'll come back to this
/* eslint-disable */
import React from 'react'; import React from 'react';
import LinkTransaction from 'component/linkTransaction'; import LinkTransaction from 'component/linkTransaction';
import { CreditAmount } from 'component/common'; import CreditAmount from 'component/common/credit-amount';
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
import Link from 'component/link'; import Link from 'component/link';
import { buildURI } from 'lbryURI'; import { buildURI } from 'lbryURI';
@ -89,3 +91,4 @@ class TransactionListItem extends React.PureComponent {
} }
export default TransactionListItem; export default TransactionListItem;
/* eslint-disable */

View file

@ -1,6 +1,8 @@
// I'll come back to this
/* eslint-disable */
import React from 'react'; import React from 'react';
import TransactionListItem from './internal/TransactionListItem'; import TransactionListItem from './internal/TransactionListItem';
import FormField from 'component/formField'; import { FormRow } from 'component/common/form';
import Link from 'component/link'; import Link from 'component/link';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
import * as modals from 'constants/modal_types'; import * as modals from 'constants/modal_types';
@ -44,20 +46,24 @@ class TransactionList extends React.PureComponent {
return ( return (
<div> <div>
{(transactionList.length || this.state.filter) && ( {(transactionList.length || this.state.filter) && (
<span className="sort-section"> <FormRow
{__('Filter')}{' '} prefix={__('Filter')}
<FormField type="select" onChange={this.handleFilterChanged.bind(this)}> postfix={
<option value="">{__('All')}</option> <Link fakeLink href="https://lbry.io/faq/transaction-types" label={__('Help')} />
<option value="spend">{__('Spends')}</option> }
<option value="receive">{__('Receives')}</option> render={() => (
<option value="publish">{__('Publishes')}</option> <select>
<option value="channel">{__('Channels')}</option> <option value="">{__('All')}</option>
<option value="tip">{__('Tips')}</option> <option value="spend">{__('Spends')}</option>
<option value="support">{__('Supports')}</option> <option value="receive">{__('Receives')}</option>
<option value="update">{__('Updates')}</option> <option value="publish">{__('Publishes')}</option>
</FormField>{' '} <option value="channel">{__('Channels')}</option>
<Link href="https://lbry.io/faq/transaction-types" icon={icons.HELP_CIRCLE} /> <option value="tip">{__('Tips')}</option>
</span> <option value="support">{__('Supports')}</option>
<option value="update">{__('Updates')}</option>
</select>
)}
/>
)} )}
{!transactionList.length && ( {!transactionList.length && (
<div className="empty">{emptyMessage || __('No transactions to list.')}</div> <div className="empty">{emptyMessage || __('No transactions to list.')}</div>
@ -92,3 +98,4 @@ class TransactionList extends React.PureComponent {
} }
export default TransactionList; export default TransactionList;
/* eslint-enable */

View file

@ -1,10 +1,18 @@
// @flow
import React from 'react'; import React from 'react';
import { BusyMessage } from 'component/common'; import { BusyMessage } from 'component/common';
import Link from 'component/link'; import Button from 'component/link';
import TransactionList from 'component/transactionList'; import TransactionList from 'component/transactionList';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
class TransactionListRecent extends React.PureComponent { type Props = {
fetchTransactions: () => void,
fetchingTransactions: boolean,
hasTransactions: boolean,
transactions: Array<{}>, // Will iron this out when I work on transactions page - Sean
};
class TransactionListRecent extends React.PureComponent<Props> {
componentWillMount() { componentWillMount() {
this.props.fetchTransactions(); this.props.fetchTransactions();
} }
@ -13,28 +21,20 @@ class TransactionListRecent extends React.PureComponent {
const { fetchingTransactions, hasTransactions, transactions } = this.props; const { fetchingTransactions, hasTransactions, transactions } = this.props;
return ( return (
<section className="card"> <section className="card card--section">
<div className="card__title-primary"> <h2>{__('Recent Transactions')}</h2>
<h3>{__('Recent Transactions')}</h3>
</div>
<div className="card__content"> <div className="card__content">
{fetchingTransactions && <BusyMessage message={__('Loading transactions')} />} {fetchingTransactions && <BusyMessage message={__('Loading transactions')} />}
{!fetchingTransactions && ( {!fetchingTransactions && (
<TransactionList <TransactionList
transactions={transactions} transactions={transactions}
emptyMessage={__('You have no recent transactions.')} emptyMessage={__("Looks like you don't have any recent transactions.")}
/> />
)} )}
</div> </div>
{hasTransactions && ( {hasTransactions && (
<div className="card__actions card__actions--bottom"> <div className="card__actions">
<Link <Button navigate="/history" label={__('Full History')} icon="Clock" />
navigate="/history"
label={__('Full History')}
icon={icons.HISTORY}
className="no-underline"
button="text"
/>
</div> </div>
)} )}
</section> </section>

View file

@ -3,7 +3,7 @@ import React from 'react';
import Button from 'component/link'; import Button from 'component/link';
import { buildURI } from 'lbryURI'; import { buildURI } from 'lbryURI';
import classnames from 'classnames'; import classnames from 'classnames';
import Icon from 'component/common/icon'; // import Icon from 'component/common/icon';
type Props = { type Props = {
isResolvingUri: boolean, isResolvingUri: boolean,
@ -57,7 +57,8 @@ class UriIndicator extends React.PureComponent<Props> {
return <span>Anonymous</span>; return <span>Anonymous</span>;
} }
let icon; // I'll look at this when working on the file page
// let icon;
let channelLink; let channelLink;
let modifier; let modifier;
@ -65,8 +66,8 @@ class UriIndicator extends React.PureComponent<Props> {
modifier = 'valid'; modifier = 'valid';
channelLink = link ? buildURI({ channelName, claimId: channelClaimId }, false) : false; channelLink = link ? buildURI({ channelName, claimId: channelClaimId }, false) : false;
} else { } else {
icon = 'icon-times-circle'; // icon = 'icon-times-circle';
modifier = 'invalid'; // modifier = 'invalid';
} }
// {!signatureIsValid ? ( // {!signatureIsValid ? (
@ -94,7 +95,7 @@ class UriIndicator extends React.PureComponent<Props> {
} }
return ( return (
<Button navigate="/show" navigateParams={{ uri: channelLink }} fakeLink> <Button navigate="/show" navigateParams={{ uri: channelLink }} noStyle>
{inner} {inner}
</Button> </Button>
); );

View file

@ -1,5 +1,7 @@
// I'll come back to this
/* eslint-disable */
import React from 'react'; import React from 'react';
import { Form, FormRow, Submit } from 'component/form.js'; import { Form, FormRow, Submit } from 'component/common/form';
class UserEmailNew extends React.PureComponent { class UserEmailNew extends React.PureComponent {
constructor(props) { constructor(props) {
@ -53,3 +55,4 @@ class UserEmailNew extends React.PureComponent {
} }
export default UserEmailNew; export default UserEmailNew;
/* eslint-enable */

View file

@ -1,6 +1,8 @@
// I'll come back to this
/* eslint-disable */
import React from 'react'; import React from 'react';
import Link from 'component/link'; import Link from 'component/link';
import { Form, FormRow, Submit } from 'component/form.js'; import { Form, FormField, Submit } from 'component/common/form';
class UserEmailVerify extends React.PureComponent { class UserEmailVerify extends React.PureComponent {
constructor(props) { constructor(props) {
@ -29,19 +31,22 @@ class UserEmailVerify extends React.PureComponent {
render() { render() {
const { cancelButton, errorMessage, email, isPending } = this.props; const { cancelButton, errorMessage, email, isPending } = this.props;
// <FormField
// label={__('Verification Code')}
// errorMessage={errorMessage}
// render{() => (
// <input
// name="code"
// value={this.state.code}
// onChange={event => {
// this.handleCodeChanged(event);
// }}
// />
// )}
// />
return ( return (
<Form onSubmit={this.handleSubmit.bind(this)}> <Form onSubmit={this.handleSubmit.bind(this)}>
<p>Please enter the verification code emailed to {email}.</p> <p>Please enter the verification code emailed to {email}.</p>
<FormRow
type="text"
label={__('Verification Code')}
name="code"
value={this.state.code}
onChange={event => {
this.handleCodeChanged(event);
}}
errorMessage={errorMessage}
/>
{/* render help separately so it always shows */} {/* render help separately so it always shows */}
<div className="form-field__helper"> <div className="form-field__helper">
<p> <p>
@ -60,3 +65,4 @@ class UserEmailVerify extends React.PureComponent {
} }
export default UserEmailVerify; export default UserEmailVerify;
/* eslint-enable */

View file

@ -1,6 +1,7 @@
// I'll come back to this
/* eslint-disable */
import React from 'react'; import React from 'react';
import { Form, FormRow, Submit } from 'component/form.js'; import { Form, FormRow, FormField } from 'component/common/form';
import FormField from 'component/formField';
const os = require('os').type(); const os = require('os').type();
const countryCodes = require('country-data') const countryCodes = require('country-data')
@ -77,29 +78,36 @@ class UserPhoneNew extends React.PureComponent {
</p> </p>
<Form onSubmit={this.handleSubmit.bind(this)}> <Form onSubmit={this.handleSubmit.bind(this)}>
<div className="form-row-phone"> <div className="form-row-phone">
<FormField type="select" onChange={this.handleSelect.bind(this)}> <FormField
{countryCodes.map((country, index) => ( onChange={this.handleSelect.bind(this)}
<option key={index} value={country.countryCallingCode}> render={() => (
{os === 'Darwin' ? country.emoji : `(${country.alpha2})`}{' '} <select>
{country.countryCallingCode} {countryCodes.map((country, index) => (
</option> <option key={index} value={country.countryCallingCode}>
))} {os === 'Darwin' ? country.emoji : `(${country.alpha2})`}{' '}
</FormField> {country.countryCallingCode}
<FormRow </option>
type="text" ))}
placeholder={this.state.country_code === '+1' ? '(555) 555-5555' : '5555555555'} </select>
name="phone" )}
value={this.state.phone} />
<FormField
errorMessage={phoneErrorMessage} errorMessage={phoneErrorMessage}
onChange={event => { onChange={event => {
this.handleChanged(event); this.handleChanged(event);
}} }}
render={() => (
<input
type="text"
placeholder={this.state.country_code === '+1' ? '(555) 555-5555' : '5555555555'}
name="phone"
value={this.state.phone}
/>
)}
/> />
</div> </div>
<div className="form-row-submit"> <Submit label="Submit" disabled={isPending} />
<Submit label="Submit" disabled={isPending} /> {cancelButton}
{cancelButton}
</div>
</Form> </Form>
</div> </div>
); );
@ -107,3 +115,4 @@ class UserPhoneNew extends React.PureComponent {
} }
export default UserPhoneNew; export default UserPhoneNew;
/* eslint-enable */

View file

@ -1,6 +1,8 @@
// I'll come back to this
/* eslint-disable */
import React from 'react'; import React from 'react';
import Link from 'component/link'; import Link from 'component/link';
import { Form, FormRow, Submit } from 'component/form.js'; import { Form, FormElement, Submit } from 'component/common/form';
class UserPhoneVerify extends React.PureComponent { class UserPhoneVerify extends React.PureComponent {
constructor(props) { constructor(props) {
@ -33,21 +35,23 @@ class UserPhoneVerify extends React.PureComponent {
<Form onSubmit={this.handleSubmit.bind(this)}> <Form onSubmit={this.handleSubmit.bind(this)}>
<p> <p>
{__( {__(
`Please enter the verification code sent to +${countryCode}${ `Please enter the verification code sent to +${countryCode}${phone}. Didn't receive it? `
phone
}. Didn't receive it? `
)} )}
<Link onClick={this.reset.bind(this)} label="Go back." /> <Link onClick={this.reset.bind(this)} label="Go back." />
</p> </p>
<FormRow <FormElement
type="text"
label={__('Verification Code')} label={__('Verification Code')}
name="code"
value={this.state.code}
onChange={event => {
this.handleCodeChanged(event);
}}
errorMessage={phoneErrorMessage} errorMessage={phoneErrorMessage}
render={() => (
<input
type="text"
name="code"
value={this.state.code}
onChange={event => {
this.handleCodeChanged(event);
}}
/>
)}
/> />
{/* render help separately so it always shows */} {/* render help separately so it always shows */}
<div className="form-field__helper"> <div className="form-field__helper">
@ -67,3 +71,4 @@ class UserPhoneVerify extends React.PureComponent {
} }
export default UserPhoneVerify; export default UserPhoneVerify;
/* eslint-enable */

View file

@ -1,3 +1,5 @@
// I'll come back to This
/* eslint-disable */
import React from 'react'; import React from 'react';
import Link from 'component/link'; import Link from 'component/link';
import CardVerify from 'component/cardVerify'; import CardVerify from 'component/cardVerify';
@ -25,8 +27,8 @@ class UserVerify extends React.PureComponent {
render() { render() {
const { errorMessage, isPending, navigate, verifyPhone, modal } = this.props; const { errorMessage, isPending, navigate, verifyPhone, modal } = this.props;
return ( return (
<div> <React.Fragment>
<section className="card card--form"> <section className="card card--section">
<div className="card__title-primary"> <div className="card__title-primary">
<h1>{__('Final Human Proof')}</h1> <h1>{__('Final Human Proof')}</h1>
</div> </div>
@ -36,7 +38,7 @@ class UserVerify extends React.PureComponent {
</p> </p>
</div> </div>
</section> </section>
<section className="card card--form"> <section className="card card--section">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__('1) Proof via Credit')}</h3> <h3>{__('1) Proof via Credit')}</h3>
</div> </div>
@ -64,7 +66,7 @@ class UserVerify extends React.PureComponent {
</div> </div>
</div> </div>
</section> </section>
<section className="card card--form"> <section className="card card--section">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__('2) Proof via Phone')}</h3> <h3>{__('2) Proof via Phone')}</h3>
</div> </div>
@ -117,7 +119,7 @@ class UserVerify extends React.PureComponent {
</div> </div>
</div> </div>
</section> </section>
<section className="card card--form"> <section className="card card--section">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__('4) Proof via Chat')}</h3> <h3>{__('4) Proof via Chat')}</h3>
</div> </div>
@ -142,7 +144,7 @@ class UserVerify extends React.PureComponent {
/> />
</div> </div>
</section> </section>
<section className="card card--form"> <section className="card card--section">
<div className="card__title-primary"> <div className="card__title-primary">
<h5>{__('Or, Skip It Entirely')}</h5> <h5>{__('Or, Skip It Entirely')}</h5>
</div> </div>
@ -157,9 +159,10 @@ class UserVerify extends React.PureComponent {
<Link onClick={() => navigate('/discover')} button="alt" label={__('Skip Rewards')} /> <Link onClick={() => navigate('/discover')} button="alt" label={__('Skip Rewards')} />
</div> </div>
</section> </section>
</div> </React.Fragment>
); );
} }
} }
export default UserVerify; export default UserVerify;
/* eslint-enable */

View file

@ -1,8 +1,16 @@
// @flow
import React from 'react'; import React from 'react';
import Link from 'component/link'; import Link from 'component/link';
import Address from 'component/address'; import Address from 'component/address';
class WalletAddress extends React.PureComponent { type Props = {
checkAddressIsMine: string => void,
receiveAddress: string,
getNewAddress: () => void,
gettingNewAddress: boolean,
};
class WalletAddress extends React.PureComponent<Props> {
componentWillMount() { componentWillMount() {
this.props.checkAddressIsMine(this.props.receiveAddress); this.props.checkAddressIsMine(this.props.receiveAddress);
} }
@ -11,21 +19,22 @@ class WalletAddress extends React.PureComponent {
const { receiveAddress, getNewAddress, gettingNewAddress } = this.props; const { receiveAddress, getNewAddress, gettingNewAddress } = this.props;
return ( return (
<section className="card"> <section className="card card--section">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__('Receive Credits')}</h3> <h2>{__('Receive Credits')}</h2>
</div> </div>
<p className="card__subtitle">
{__('Use this wallet address to receive credits sent by another user (or yourself).')}
</p>
<div className="card__content"> <div className="card__content">
<p>
{__('Use this wallet address to receive credits sent by another user (or yourself).')}
</p>
<Address address={receiveAddress} showCopyButton /> <Address address={receiveAddress} showCopyButton />
</div> </div>
<div className="card__actions"> <div className="card__actions">
<Link <Link
label={__('Get New Address')} label={__('Get New Address')}
button="primary" icon="RefreshCw"
icon="icon-refresh"
onClick={getNewAddress} onClick={getNewAddress}
disabled={gettingNewAddress} disabled={gettingNewAddress}
/> />

View file

@ -1,33 +1,19 @@
// @flow
import React from 'react'; import React from 'react';
import Link from 'component/link'; import CreditAmount from 'component/common/credit-amount';
import { CreditAmount } from 'component/common';
const WalletBalance = props => { type Props = {
const { balance, navigate } = props; balance: number,
/* };
<div className="help">
<Link const WalletBalance = (props: Props) => {
onClick={() => navigate("/backup")} const { balance } = props;
label={__("Backup Your Wallet")}
/>
</div>
*/
return ( return (
<section className="card"> <section className="card card--section">
<div className="card__title-primary"> <h2>{__('Balance')}</h2>
<h3>{__('Balance')}</h3> <span className="card__subtitle">{__('You currently have')}</span>
</div>
<div className="card__content"> <div className="card__content">
{(balance || balance === 0) && <CreditAmount amount={balance} precision={8} />} {(balance || balance === 0) && <CreditAmount large amount={balance} precision={8} />}
</div>
<div className="card__actions">
<Link button="alt" navigate="/getcredits" label={__('Get Credits')} />
<Link
button="alt"
disabled={balance === 0}
navigate="/backup"
label={__('Backup Wallet')}
/>
</div> </div>
</section> </section>
); );

View file

@ -1,28 +1,9 @@
import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { doSendDraftTransaction } from 'redux/actions/wallet';
doSendDraftTransaction,
doSetDraftTransactionAmount,
doSetDraftTransactionAddress,
} from 'redux/actions/wallet';
import {
selectDraftTransactionAmount,
selectDraftTransactionAddress,
selectDraftTransactionError,
} from 'redux/selectors/wallet';
import WalletSend from './view'; import WalletSend from './view';
const select = state => ({
address: selectDraftTransactionAddress(state),
amount: selectDraftTransactionAmount(state),
error: selectDraftTransactionError(state),
});
const perform = dispatch => ({ const perform = dispatch => ({
sendToAddress: () => dispatch(doSendDraftTransaction()), sendToAddress: values => dispatch(doSendDraftTransaction(values)),
setAmount: event => dispatch(doSetDraftTransactionAmount(event.target.value)),
setAddress: event => dispatch(doSetDraftTransactionAddress(event.target.value)),
}); });
export default connect(select, perform)(WalletSend); export default connect(null, perform)(WalletSend);

View file

@ -1,55 +1,105 @@
// @flow
import React from 'react'; import React from 'react';
import { Form, FormRow, Submit } from 'component/form'; import Button from 'component/link';
import { regexAddress } from 'lbryURI'; import { Form, FormRow, FormField } from 'component/common/form';
import { Formik } from 'formik';
import { validateSendTx } from 'util/form-validation';
class WalletSend extends React.PureComponent { type DraftTransaction = {
handleSubmit() { address: string,
const { amount, address, sendToAddress } = this.props; amount: number | string, // So we can use a placeholder in the input
const validSubmit = parseFloat(amount) > 0.0 && address; };
if (validSubmit) { type Props = {
sendToAddress(); sendToAddress: DraftTransaction => void,
} };
class WalletSend extends React.PureComponent<Props> {
constructor() {
super();
(this: any).handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(values: DraftTransaction) {
const { sendToAddress } = this.props;
sendToAddress(values);
} }
render() { render() {
const { closeModal, modal, setAmount, setAddress, amount, address, error } = this.props;
return ( return (
<section className="card"> <section className="card card--section">
<Form onSubmit={this.handleSubmit.bind(this)}> <div className="card__title-primary">
<div className="card__title-primary"> <h2>{__('Send Credits')}</h2>
<h3>{__('Send Credits')}</h3> </div>
</div> <div className="card__content">
<div className="card__content"> <Formik
<FormRow initialValues={{
label={__('Amount')} address: '',
postfix={__('LBC')} amount: '',
step="any" }}
min="0" onSubmit={this.handleSubmit}
type="number" validate={validateSendTx}
placeholder="1.23" render={({
size="10" values,
onChange={setAmount} errors,
value={amount} touched,
/> handleChange,
</div> handleBlur,
<div className="card__content"> handleSubmit,
<FormRow isSubmitting,
label={__('Recipient Address')} }) => (
placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs" <Form onSubmit={handleSubmit}>
type="text" <FormRow>
size="60" <FormField
onChange={setAddress} label={__('Amount')}
value={address} postfix={__('LBC')}
regexp={regexAddress} error={!!values.amount && touched.amount && errors.amount}
trim render={() => (
/> <input
<div className="form-row-submit"> className="input--lbc-amount"
<Submit label={__('Send')} disabled={!(parseFloat(amount) > 0.0) || !address} /> type="number"
</div> name="amount"
</div> min="0"
</Form> onChange={handleChange}
onBlur={handleBlur}
value={values.amount}
/>
)}
/>
<FormField
label={__('Recipient address')}
error={!!values.address && touched.address && errors.address}
render={() => (
<input
className="input--address"
type="text"
name="address"
placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs"
onChange={handleChange}
onBlur={handleBlur}
value={values.address}
/>
)}
/>
</FormRow>
<div className="card__actions">
<Button
type="submit"
icon="Send"
label={__('Send')}
disabled={
!values.address ||
!!Object.keys(errors).length ||
!(parseFloat(values.amount) > 0.0)
}
/>
</div>
</Form>
)}
/>
</div>
</section> </section>
); );
} }

View file

@ -1,6 +1,8 @@
// I'll come back to this
/* eslint-disable */
import React from 'react'; import React from 'react';
import Link from 'component/link'; import Link from 'component/link';
import { FormRow } from 'component/form'; import { FormRow } from 'component/common/form';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
class WalletSendTip extends React.PureComponent { class WalletSendTip extends React.PureComponent {
@ -67,3 +69,4 @@ class WalletSendTip extends React.PureComponent {
} }
export default WalletSendTip; export default WalletSendTip;
/* eslint-enable */

View file

@ -466,6 +466,7 @@ export default class Autocomplete extends React.Component {
const menu = this.props.renderMenu(items, this.props.value, style); const menu = this.props.renderMenu(items, this.props.value, style);
return React.cloneElement(menu, { return React.cloneElement(menu, {
ref: e => (this.refs.menu = e), ref: e => (this.refs.menu = e),
className: 'wunderbar__menu',
// Ignore blur to prevent menu from de-rendering before we can process click // Ignore blur to prevent menu from de-rendering before we can process click
onMouseEnter: () => this.setIgnoreBlur(true), onMouseEnter: () => this.setIgnoreBlur(true),
onMouseLeave: () => this.setIgnoreBlur(false), onMouseLeave: () => this.setIgnoreBlur(false),

View file

@ -2,6 +2,8 @@
import React from 'react'; import React from 'react';
import { normalizeURI } from 'lbryURI'; import { normalizeURI } from 'lbryURI';
import classnames from 'classnames'; import classnames from 'classnames';
import throttle from 'util/throttle';
import Icon from 'component/common/icon';
import Autocomplete from './internal/autocomplete'; import Autocomplete from './internal/autocomplete';
type Props = { type Props = {
@ -16,29 +18,22 @@ type Props = {
}; };
class WunderBar extends React.PureComponent<Props> { class WunderBar extends React.PureComponent<Props> {
constructor() { constructor(props: Props) {
super(); super(props);
(this: any).handleSubmit = this.handleSubmit.bind(this); (this: any).handleSubmit = this.handleSubmit.bind(this);
(this: any).handleChange = this.handleChange.bind(this); (this: any).handleChange = this.handleChange.bind(this);
(this: any).focus = this.focus.bind(this); (this: any).focus = this.focus.bind(this);
(this: any).throttledGetSearchSuggestions = throttle(this.props.getSearchSuggestions, 1000);
this.input = undefined; this.input = undefined;
} }
input: ?HTMLInputElement;
handleChange(e: SyntheticInputEvent<*>) { handleChange(e: SyntheticInputEvent<*>) {
const { updateSearchQuery, getSearchSuggestions } = this.props; const { updateSearchQuery, getSearchSuggestions } = this.props;
const { value } = e.target; const { value } = e.target;
updateSearchQuery(value); updateSearchQuery(value);
getSearchSuggestions(value); this.throttledGetSearchSuggestions(value);
}
focus() {
const { input } = this;
if (input) {
input.focus();
}
} }
handleSubmit(value: string) { handleSubmit(value: string) {
@ -70,6 +65,16 @@ class WunderBar extends React.PureComponent<Props> {
} }
} }
focus() {
const { input } = this;
if (input) {
input.focus();
}
}
input: ?HTMLInputElement;
throttledGetSearchSuggestions: string => void;
render() { render() {
const { searchQuery, isActive, address, suggestions } = this.props; const { searchQuery, isActive, address, suggestions } = this.props;
@ -83,12 +88,13 @@ class WunderBar extends React.PureComponent<Props> {
'header__wunderbar--active': isActive, 'header__wunderbar--active': isActive,
})} })}
> >
<Icon icon="Search" />
<Autocomplete <Autocomplete
autoHighlight autoHighlight
ref={ref => { ref={ref => {
this.input = ref; this.input = ref;
}} }}
wrapperStyle={{ flex: 1, minHeight: 0 }} wrapperStyle={{ flex: 1 }}
value={wunderbarValue} value={wunderbarValue}
items={suggestions} items={suggestions}
getItemValue={item => item.value} getItemValue={item => item.value}
@ -108,7 +114,8 @@ class WunderBar extends React.PureComponent<Props> {
'wunderbar__active-suggestion': isHighlighted, 'wunderbar__active-suggestion': isHighlighted,
})} })}
> >
{item.label} <Icon icon={item.icon} />
<span className="wunderbar__suggestion-label">{item.label}</span>
</div> </div>
)} )}
/> />

View file

@ -37,8 +37,6 @@ export const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED';
export const UPDATE_BALANCE = 'UPDATE_BALANCE'; export const UPDATE_BALANCE = 'UPDATE_BALANCE';
export const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED'; export const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED';
export const CHECK_ADDRESS_IS_MINE_COMPLETED = 'CHECK_ADDRESS_IS_MINE_COMPLETED'; export const CHECK_ADDRESS_IS_MINE_COMPLETED = 'CHECK_ADDRESS_IS_MINE_COMPLETED';
export const SET_DRAFT_TRANSACTION_AMOUNT = 'SET_DRAFT_TRANSACTION_AMOUNT';
export const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS';
export const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED'; export const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED';
export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED'; export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED';
export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED'; export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED';

View file

@ -1,5 +1,5 @@
export const FEATURED = 'rocket'; export const FEATURED = 'Award';
export const LOCAL = 'folder'; export const LOCAL = 'Folder';
export const FILE = 'file'; export const FILE = 'file';
export const HISTORY = 'history'; export const HISTORY = 'history';
export const HELP_CIRCLE = 'question-circle'; export const HELP_CIRCLE = 'question-circle';

View file

@ -1,6 +1,9 @@
// I'll come back to this
/* esline-disable */
import React from 'react'; import React from 'react';
import { Modal } from 'modal/modal'; import { Modal } from 'modal/modal';
import { CreditAmount, CurrencySymbol } from 'component/common'; import { CurrencySymbol } from 'component/common';
import CreditAmount from 'component/common/credit-amount';
import Link from 'component/link/index'; import Link from 'component/link/index';
const ModalCreditIntro = props => { const ModalCreditIntro = props => {
@ -46,3 +49,4 @@ const ModalCreditIntro = props => {
}; };
export default ModalCreditIntro; export default ModalCreditIntro;
/* esline-enable */

View file

@ -1,6 +1,8 @@
// I"ll come back to This
/* esline-disable */
import React from 'react'; import React from 'react';
import { Modal } from 'modal/modal'; import { Modal } from 'modal/modal';
import { CreditAmount } from 'component/common'; import CreditAmount from 'component/common/credit-amount';
class ModalFirstReward extends React.PureComponent { class ModalFirstReward extends React.PureComponent {
render() { render() {
@ -42,3 +44,4 @@ class ModalFirstReward extends React.PureComponent {
} }
export default ModalFirstReward; export default ModalFirstReward;
/* eslint-enable */

View file

@ -4,6 +4,7 @@ import Link from 'component/link';
import UserEmailNew from 'component/userEmailNew'; import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify'; import UserEmailVerify from 'component/userEmailVerify';
import UserVerify from 'component/userVerify'; import UserVerify from 'component/userVerify';
import Page from 'component/page';
export class AuthPage extends React.PureComponent { export class AuthPage extends React.PureComponent {
componentWillMount() { componentWillMount() {
@ -58,25 +59,27 @@ export class AuthPage extends React.PureComponent {
const { email, user, isPending, navigate } = this.props; const { email, user, isPending, navigate } = this.props;
const [innerContent, useTemplate] = this.renderMain(); const [innerContent, useTemplate] = this.renderMain();
return useTemplate ? ( return (
<main> <Page>
<section className="card card--form"> {useTemplate ? (
<div className="card__title-primary"> <section className="card card--section">
<h1>{this.getTitle()}</h1> <div className="card__title-primary">
</div> <h1>{this.getTitle()}</h1>
<div className="card__content">{innerContent}</div>
<div className="card__content">
<div className="help">
{`${__(
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards.'
)} `}
<Link onClick={() => navigate('/discover')} label={__('Return home')} />.
</div> </div>
</div> <div className="card__content">{innerContent}</div>
</section> <div className="card__content">
</main> <div className="help">
) : ( {`${__(
innerContent 'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards.'
)} `}
<Link onClick={() => navigate('/discover')} label={__('Return home')} />.
</div>
</div>
</section>
) : (
innerContent
)}
</Page>
); );
} }
} }

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import SubHeader from 'component/subHeader';
import Link from 'component/link'; import Link from 'component/link';
import Page from 'component/page';
class BackupPage extends React.PureComponent { class BackupPage extends React.PureComponent {
render() { render() {
@ -16,9 +16,8 @@ class BackupPage extends React.PureComponent {
} }
return ( return (
<main className="main--single-column"> <Page>
<SubHeader /> <section className="card card--section">
<section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__('Backup Your LBRY Credits')}</h3> <h3>{__('Backup Your LBRY Credits')}</h3>
</div> </div>
@ -57,7 +56,7 @@ class BackupPage extends React.PureComponent {
</p> </p>
</div> </div>
</section> </section>
</main> </Page>
); );
} }
} }

View file

@ -3,7 +3,6 @@ import Link from 'component/link';
import { FileTile } from 'component/fileTile'; import { FileTile } from 'component/fileTile';
import { BusyMessage, Thumbnail } from 'component/common.js'; import { BusyMessage, Thumbnail } from 'component/common.js';
import FileList from 'component/fileList'; import FileList from 'component/fileList';
import SubHeader from 'component/subHeader';
class FileListDownloaded extends React.PureComponent { class FileListDownloaded extends React.PureComponent {
componentWillMount() { componentWillMount() {
@ -31,12 +30,7 @@ class FileListDownloaded extends React.PureComponent {
); );
} }
return ( return <main className="main--single-column">{content}</main>;
<main className="main--single-column">
<SubHeader />
{content}
</main>
);
} }
} }

View file

@ -3,7 +3,6 @@ import Link from 'component/link';
import FileTile from 'component/fileTile'; import FileTile from 'component/fileTile';
import { BusyMessage, Thumbnail } from 'component/common.js'; import { BusyMessage, Thumbnail } from 'component/common.js';
import FileList from 'component/fileList'; import FileList from 'component/fileList';
import SubHeader from 'component/subHeader';
class FileListPublished extends React.PureComponent { class FileListPublished extends React.PureComponent {
componentWillMount() { componentWillMount() {
@ -41,12 +40,7 @@ class FileListPublished extends React.PureComponent {
); );
} }
return ( return <main className="main--single-column">{content}</main>;
<main className="main--single-column">
<SubHeader />
{content}
</main>
);
} }
} }

View file

@ -1,25 +1,24 @@
import React from 'react'; import React from 'react';
import SubHeader from 'component/subHeader';
import Link from 'component/link'; import Link from 'component/link';
import RewardSummary from 'component/rewardSummary'; import RewardSummary from 'component/rewardSummary';
import ShapeShift from 'component/shapeShift'; import ShapeShift from 'component/shapeShift';
import Page from 'component/page';
const GetCreditsPage = props => ( const GetCreditsPage = props => (
<main className="main--single-column"> <Page>
<SubHeader />
<RewardSummary /> <RewardSummary />
<ShapeShift /> <ShapeShift />
<section className="card"> <section className="card card--section">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__('From External Wallet')}</h3> <h2>{__('From External Wallet')}</h2>
</div> </div>
<div className="card__actions"> <div className="card__actions">
<Link button="alt" navigate="/send" icon="icon-send" label={__('Send / Receive')} /> <Link navigate="/send" label={__('Send / Receive')} />
</div> </div>
</section> </section>
<section className="card"> <section className="card card--section">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__('More ways to get LBRY Credits')}</h3> <h2>{__('More ways to get LBRY Credits')}</h2>
</div> </div>
<div className="card__content"> <div className="card__content">
<p> <p>
@ -29,10 +28,10 @@ const GetCreditsPage = props => (
</p> </p>
</div> </div>
<div className="card__actions"> <div className="card__actions">
<Link button="alt" href="https://lbry.io/faq/earn-credits" label={__('Read More')} /> <Link fakeLink href="https://lbry.io/faq/earn-credits" label={__('Read More')} />
</div> </div>
</section> </section>
</main> </Page>
); );
export default GetCreditsPage; export default GetCreditsPage;

View file

@ -2,9 +2,9 @@
import React from 'react'; import React from 'react';
import lbry from 'lbry.js'; import lbry from 'lbry.js';
import Link from 'component/link'; import Link from 'component/link';
import SubHeader from 'component/subHeader';
import { BusyMessage } from 'component/common'; import { BusyMessage } from 'component/common';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import Page from 'component/page';
class HelpPage extends React.PureComponent { class HelpPage extends React.PureComponent {
constructor(props) { constructor(props) {
@ -70,8 +70,7 @@ class HelpPage extends React.PureComponent {
} }
return ( return (
<main className="main--single-column"> <Page>
<SubHeader />
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__('Read the FAQ')}</h3> <h3>{__('Read the FAQ')}</h3>
@ -210,7 +209,7 @@ class HelpPage extends React.PureComponent {
)} )}
</div> </div>
</section> </section>
</main> </Page>
); );
} }
} }

View file

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { BusyMessage } from 'component/common'; import { BusyMessage } from 'component/common';
import SubHeader from 'component/subHeader';
import InviteNew from 'component/inviteNew'; import InviteNew from 'component/inviteNew';
import InviteList from 'component/inviteList'; import InviteList from 'component/inviteList';
@ -14,7 +13,6 @@ class InvitePage extends React.PureComponent {
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<SubHeader />
{isPending && <BusyMessage message={__('Checking your invite status')} />} {isPending && <BusyMessage message={__('Checking your invite status')} />}
{!isPending && {!isPending &&
isFailed && <span className="empty">{__('Failed to retrieve invite status.')}</span>} isFailed && <span className="empty">{__('Failed to retrieve invite status.')}</span>}

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import Link from 'component/link'; import Link from 'component/link';
import { FormRow } from 'component/form'; import { FormRow } from 'component/common/form';
import { doShowSnackBar } from 'redux/actions/app'; import { doShowSnackBar } from 'redux/actions/app';
import lbry from '../lbry.js'; import lbry from '../lbry.js';

View file

@ -2,8 +2,8 @@ import React from 'react';
import { BusyMessage } from 'component/common'; import { BusyMessage } from 'component/common';
import RewardListClaimed from 'component/rewardListClaimed'; import RewardListClaimed from 'component/rewardListClaimed';
import RewardTile from 'component/rewardTile'; import RewardTile from 'component/rewardTile';
import SubHeader from 'component/subHeader';
import Link from 'component/link'; import Link from 'component/link';
import Page from 'component/page';
class RewardsPage extends React.PureComponent { class RewardsPage extends React.PureComponent {
/* /*
@ -34,7 +34,7 @@ class RewardsPage extends React.PureComponent {
if (user && !user.is_reward_approved) { if (user && !user.is_reward_approved) {
if (!user.primary_email || !user.has_verified_email || !user.is_identity_verified) { if (!user.primary_email || !user.has_verified_email || !user.is_identity_verified) {
return ( return (
<section className="card"> <section className="card card--section">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__('Humans Only')}</h3> <h3>{__('Humans Only')}</h3>
</div> </div>
@ -102,6 +102,7 @@ class RewardsPage extends React.PureComponent {
); );
} }
return ( return (
// TODO: come back to me and actually implement a grid
<div className="card-grid"> <div className="card-grid">
{rewards.map(reward => <RewardTile key={reward.reward_type} reward={reward} />)} {rewards.map(reward => <RewardTile key={reward.reward_type} reward={reward} />)}
</div> </div>
@ -110,12 +111,11 @@ class RewardsPage extends React.PureComponent {
render() { render() {
return ( return (
<main className="main--single-column"> <Page>
<SubHeader />
{this.renderPageHeader()} {this.renderPageHeader()}
{this.renderUnclaimedRewards()} {this.renderUnclaimedRewards()}
{<RewardListClaimed />} {<RewardListClaimed />}
</main> </Page>
); );
} }
} }

View file

@ -1,14 +1,13 @@
import React from 'react'; import React from 'react';
import SubHeader from 'component/subHeader';
import WalletSend from 'component/walletSend'; import WalletSend from 'component/walletSend';
import WalletAddress from 'component/walletAddress'; import WalletAddress from 'component/walletAddress';
import Page from 'component/page';
const SendReceivePage = props => ( const SendReceivePage = props => (
<main className="main--single-column"> <Page>
<SubHeader />
<WalletSend /> <WalletSend />
<WalletAddress /> <WalletAddress />
</main> </Page>
); );
export default SendReceivePage; export default SendReceivePage;

View file

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import FormField from 'component/formField'; import FormField from 'component/formField';
import { FormRow } from 'component/form.js'; import { FormRow } from 'component/common/form';
import SubHeader from 'component/subHeader';
import * as settings from 'constants/settings'; import * as settings from 'constants/settings';
import lbry from 'lbry.js'; import lbry from 'lbry.js';
import Link from 'component/link'; import Link from 'component/link';
import FormFieldPrice from 'component/formFieldPrice'; import FormFieldPrice from 'component/formFieldPrice';
import Page from 'component/page';
class SettingsPage extends React.PureComponent { class SettingsPage extends React.PureComponent {
constructor(props) { constructor(props) {
@ -144,8 +144,7 @@ class SettingsPage extends React.PureComponent {
); );
} }
return ( return (
<main className="main--single-column"> <Page>
<SubHeader />
{/* {/*
<section className="card"> <section className="card">
<div className="card__content"> <div className="card__content">
@ -348,7 +347,7 @@ class SettingsPage extends React.PureComponent {
</p> </p>
</div> </div>
</section> </section>
</main> </Page>
); );
} }
} }

View file

@ -1,6 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import SubHeader from 'component/subHeader'; import Page from 'component/page';
import { BusyMessage } from 'component/common'; import { BusyMessage } from 'component/common';
import CategoryList from 'component/common/category-list'; import CategoryList from 'component/common/category-list';
import type { Subscription } from 'redux/reducers/subscriptions'; import type { Subscription } from 'redux/reducers/subscriptions';
@ -61,16 +61,10 @@ export default class extends React.PureComponent<Props> {
(subscriptions.length !== savedSubscriptions.length || someClaimsNotLoaded); (subscriptions.length !== savedSubscriptions.length || someClaimsNotLoaded);
return ( return (
<main className="main main--no-margin"> <Page noPadding isLoading={fetchingSubscriptions}>
<SubHeader fullWidth smallMargin />
{!savedSubscriptions.length && ( {!savedSubscriptions.length && (
<span>{__("You haven't subscribed to any channels yet")}</span> <span>{__("You haven't subscribed to any channels yet")}</span>
)} )}
{fetchingSubscriptions && (
<div className="card-row__placeholder">
<BusyMessage message={__('Fetching subscriptions')} />
</div>
)}
{!!savedSubscriptions.length && ( {!!savedSubscriptions.length && (
<div> <div>
{!!subscriptions.length && {!!subscriptions.length &&
@ -93,7 +87,7 @@ export default class extends React.PureComponent<Props> {
})} })}
</div> </div>
)} )}
</main> </Page>
); );
} }
} }

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { BusyMessage } from 'component/common'; import { BusyMessage } from 'component/common';
import SubHeader from 'component/subHeader';
import TransactionList from 'component/transactionList'; import TransactionList from 'component/transactionList';
import Page from 'component/page';
class TransactionHistoryPage extends React.PureComponent { class TransactionHistoryPage extends React.PureComponent {
componentWillMount() { componentWillMount() {
@ -12,9 +12,8 @@ class TransactionHistoryPage extends React.PureComponent {
const { fetchingTransactions, transactions } = this.props; const { fetchingTransactions, transactions } = this.props;
return ( return (
<main className="main--single-column"> <Page>
<SubHeader /> <section className="card card--section">
<section className="card">
<div <div
className={`card__title-primary ${ className={`card__title-primary ${
fetchingTransactions && transactions.length ? 'reloading' : '' fetchingTransactions && transactions.length ? 'reloading' : ''
@ -35,7 +34,7 @@ class TransactionHistoryPage extends React.PureComponent {
)} )}
</div> </div>
</section> </section>
</main> </Page>
); );
} }
} }

View file

@ -1,18 +1,19 @@
import React from 'react'; import React from 'react';
import SubHeader from 'component/subHeader';
import WalletBalance from 'component/walletBalance'; import WalletBalance from 'component/walletBalance';
import RewardSummary from 'component/rewardSummary'; import RewardSummary from 'component/rewardSummary';
import TransactionListRecent from 'component/transactionListRecent'; import TransactionListRecent from 'component/transactionListRecent';
import WalletAddress from 'component/walletAddress';
import Page from 'component/page';
const WalletPage = props => ( const WalletPage = () => (
<main className="main--single-column page--wallet"> <Page>
<SubHeader /> <div className="columns">
<div className="card-grid">
<WalletBalance /> <WalletBalance />
<RewardSummary /> <RewardSummary />
</div> </div>
<WalletAddress />
<TransactionListRecent /> <TransactionListRecent />
</main> </Page>
); );
export default WalletPage; export default WalletPage;

View file

@ -1,5 +1,5 @@
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import { normalizeURI } from 'lbryURI'; import { normalizeURI, buildURI } from 'lbryURI';
import { doResolveUri } from 'redux/actions/content'; import { doResolveUri } from 'redux/actions/content';
import { doNavigate } from 'redux/actions/navigation'; import { doNavigate } from 'redux/actions/navigation';
import { selectCurrentPage } from 'redux/selectors/navigation'; import { selectCurrentPage } from 'redux/selectors/navigation';
@ -89,10 +89,17 @@ export const getSearchSuggestions = value => dispatch => {
fetch(`https://lighthouse.lbry.io/autocomplete?s=${searchValue}`) fetch(`https://lighthouse.lbry.io/autocomplete?s=${searchValue}`)
.then(handleSearchApiResponse) .then(handleSearchApiResponse)
.then(suggestions => { .then(suggestions => {
const formattedSuggestions = suggestions.slice(0, 5).map(suggestion => ({ const formattedSuggestions = suggestions.slice(0, 5).map(suggestion => {
label: suggestion, // This will need to be more robust when the api starts returning lbry uris
value: suggestion, const isChannel = suggestion.startsWith('@');
})); const suggestionObj = {
value: suggestion,
label: isChannel ? suggestion.slice(1) : suggestion,
icon: isChannel ? 'AtSign' : 'Search',
};
return suggestionObj;
});
// Should we add lbry://{query} as the first result? // Should we add lbry://{query} as the first result?
// If it's not a valid uri, then add a "search for {query}" result // If it's not a valid uri, then add a "search for {query}" result
@ -100,12 +107,12 @@ export const getSearchSuggestions = value => dispatch => {
try { try {
const uri = normalizeURI(value); const uri = normalizeURI(value);
formattedSuggestions.unshift( formattedSuggestions.unshift(
{ label: uri, value: uri }, { label: uri, value: uri, icon: 'Compass' },
{ label: searchLabel, value: `${value}?search` } { label: searchLabel, value: `${value}?search`, icon: 'Search' }
); );
} catch (e) { } catch (e) {
if (value) { if (value) {
formattedSuggestions.unshift({ label: searchLabel, value }); formattedSuggestions.unshift({ label: searchLabel, value, icon: 'Search' });
} }
} }

View file

@ -5,8 +5,6 @@ import { doOpenModal, doShowSnackBar } from 'redux/actions/app';
import { doNavigate } from 'redux/actions/navigation'; import { doNavigate } from 'redux/actions/navigation';
import { import {
selectBalance, selectBalance,
selectDraftTransaction,
selectDraftTransactionAmount,
} from 'redux/selectors/wallet'; } from 'redux/selectors/wallet';
export function doUpdateBalance() { export function doUpdateBalance() {
@ -89,12 +87,10 @@ export function doCheckAddressIsMine(address) {
}; };
} }
export function doSendDraftTransaction() { export function doSendDraftTransaction({ amount, address }) {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
const draftTx = selectDraftTransaction(state);
const balance = selectBalance(state); const balance = selectBalance(state);
const amount = selectDraftTransactionAmount(state);
if (balance - amount <= 0) { if (balance - amount <= 0) {
dispatch(doOpenModal(MODALS.INSUFFICIENT_CREDITS)); dispatch(doOpenModal(MODALS.INSUFFICIENT_CREDITS));
@ -135,26 +131,12 @@ export function doSendDraftTransaction() {
}; };
Lbry.wallet_send({ Lbry.wallet_send({
amount: draftTx.amount, amount,
address: draftTx.address, address,
}).then(successCallback, errorCallback); }).then(successCallback, errorCallback);
}; };
} }
export function doSetDraftTransactionAmount(amount) {
return {
type: ACTIONS.SET_DRAFT_TRANSACTION_AMOUNT,
data: { amount },
};
}
export function doSetDraftTransactionAddress(address) {
return {
type: ACTIONS.SET_DRAFT_TRANSACTION_ADDRESS,
data: { address },
};
}
export function doSendSupport(amount, claimId, uri) { export function doSendSupport(amount, claimId, uri) {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();

View file

@ -2,10 +2,6 @@ import * as ACTIONS from 'constants/action_types';
const reducers = {}; const reducers = {};
const receiveAddress = localStorage.getItem('receiveAddress'); const receiveAddress = localStorage.getItem('receiveAddress');
const buildDraftTransaction = () => ({
amount: undefined,
address: undefined,
});
const defaultState = { const defaultState = {
balance: undefined, balance: undefined,
@ -14,8 +10,8 @@ const defaultState = {
fetchingTransactions: false, fetchingTransactions: false,
receiveAddress, receiveAddress,
gettingNewAddress: false, gettingNewAddress: false,
draftTransaction: buildDraftTransaction(),
sendingSupport: false, sendingSupport: false,
sendingTx: false
}; };
reducers[ACTIONS.FETCH_TRANSACTIONS_STARTED] = state => reducers[ACTIONS.FETCH_TRANSACTIONS_STARTED] = state =>
@ -68,51 +64,21 @@ reducers[ACTIONS.CHECK_ADDRESS_IS_MINE_COMPLETED] = state =>
checkingAddressOwnership: false, checkingAddressOwnership: false,
}); });
reducers[ACTIONS.SET_DRAFT_TRANSACTION_AMOUNT] = (state, action) => {
const oldDraft = state.draftTransaction;
const newDraft = Object.assign({}, oldDraft, {
amount: parseFloat(action.data.amount),
});
return Object.assign({}, state, {
draftTransaction: newDraft,
});
};
reducers[ACTIONS.SET_DRAFT_TRANSACTION_ADDRESS] = (state, action) => {
const oldDraft = state.draftTransaction;
const newDraft = Object.assign({}, oldDraft, {
address: action.data.address,
});
return Object.assign({}, state, {
draftTransaction: newDraft,
});
};
reducers[ACTIONS.SEND_TRANSACTION_STARTED] = state => { reducers[ACTIONS.SEND_TRANSACTION_STARTED] = state => {
const newDraftTransaction = Object.assign({}, state.draftTransaction, {
sending: true,
});
return Object.assign({}, state, { return Object.assign({}, state, {
draftTransaction: newDraftTransaction, sendingTx: true,
}); });
}; };
reducers[ACTIONS.SEND_TRANSACTION_COMPLETED] = state => reducers[ACTIONS.SEND_TRANSACTION_COMPLETED] = state =>
Object.assign({}, state, { Object.assign({}, state, {
draftTransaction: buildDraftTransaction(), sendingTx: false,
}); });
reducers[ACTIONS.SEND_TRANSACTION_FAILED] = (state, action) => { reducers[ACTIONS.SEND_TRANSACTION_FAILED] = (state, action) => {
const newDraftTransaction = Object.assign({}, state.draftTransaction, {
sending: false,
error: action.data.error,
});
return Object.assign({}, state, { return Object.assign({}, state, {
draftTransaction: newDraftTransaction, sendingTx: false,
error: action.data.error
}); });
}; };

View file

@ -1,6 +1,5 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { parseQueryParams, toQueryString } from 'util/query_params'; import { parseQueryParams } from 'util/query_params';
import { normalizeURI } from 'lbryURI';
export const selectState = state => state.navigation || {}; export const selectState = state => state.navigation || {};
@ -22,72 +21,6 @@ export const selectCurrentParams = createSelector(selectCurrentPath, path => {
export const makeSelectCurrentParam = param => export const makeSelectCurrentParam = param =>
createSelector(selectCurrentParams, params => (params ? params[param] : undefined)); createSelector(selectCurrentParams, params => (params ? params[param] : undefined));
export const selectHeaderLinks = createSelector(selectCurrentPage, page => {
// This contains intentional fall throughs
switch (page) {
case 'wallet':
case 'history':
case 'send':
case 'getcredits':
case 'invite':
case 'rewards':
case 'backup':
return {
wallet: __('Overview'),
getcredits: __('Get Credits'),
send: __('Send / Receive'),
rewards: __('Rewards'),
invite: __('Invites'),
history: __('History'),
};
case 'downloaded':
case 'published':
return {
downloaded: __('Downloaded'),
published: __('Published'),
};
case 'settings':
case 'help':
return {
settings: __('Settings'),
help: __('Help'),
};
case 'discover':
case 'subscriptions':
return {
discover: __('Discover'),
subscriptions: __('Subscriptions'),
};
default:
return null;
}
});
export const selectPageTitle = createSelector(
selectCurrentPage,
selectCurrentParams,
(page, params) => {
switch (page) {
case 'show': {
const parts = [normalizeURI(params.uri)];
// If the params has any keys other than "uri"
if (Object.keys(params).length > 1) {
parts.push(toQueryString(Object.assign({}, params, { uri: null })));
}
return parts.join('?');
}
case 'discover':
return __('Discover');
case false:
case null:
case '':
return '';
default:
return '';
}
}
);
export const selectPathAfterAuth = createSelector(selectState, state => state.pathAfterAuth); export const selectPathAfterAuth = createSelector(selectState, state => state.pathAfterAuth);
export const selectIsBackDisabled = createSelector(selectState, state => state.index === 0); export const selectIsBackDisabled = createSelector(selectState, state => state.index === 0);
@ -97,6 +30,8 @@ export const selectIsForwardDisabled = createSelector(
state => state.index === state.stack.length - 1 state => state.index === state.stack.length - 1
); );
export const selectIsHome = createSelector(selectCurrentPage, page => page === 'discover');
export const selectHistoryIndex = createSelector(selectState, state => state.index); export const selectHistoryIndex = createSelector(selectState, state => state.index);
export const selectHistoryStack = createSelector(selectState, state => state.stack); export const selectHistoryStack = createSelector(selectState, state => state.stack);
@ -106,3 +41,126 @@ export const selectActiveHistoryEntry = createSelector(
selectState, selectState,
state => state.stack[state.index] state => state.stack[state.index]
); );
export const selectPageTitle = createSelector(
selectCurrentPage,
(page) => {
switch (page) {
default:
return '';
}
}
);
export const selectNavLinks = createSelector(
selectCurrentPage,
selectHistoryStack,
(currentPage, historyStack) => {
const isWalletPage = page =>
page === 'wallet' ||
page === 'send' ||
page === 'getcredits' ||
page === 'rewards' ||
page === 'history';
let walletLink;
if (isWalletPage(currentPage)) {
// If they are on a wallet page, the top level link should direct them to the overview page
walletLink = '/wallet';
} else {
// check to see if they've recently been on a wallet sub-link
const previousStack = historyStack.slice().reverse();
for (let i = 0; i < previousStack.length; i += 1) {
const currentStackItem = previousStack[i];
// Trim off the "/" from the path
const pageInStack = currentStackItem.path.slice(1);
if (isWalletPage(pageInStack)) {
walletLink = currentStackItem.path;
break;
}
}
}
const walletSubLinks = [
{
label: 'Overview',
path: '/wallet',
active: currentPage === 'wallet',
},
{
label: 'Send & Recieve',
path: '/send',
active: currentPage === 'send',
},
{
label: 'Get Credits',
path: '/getcredits',
active: currentPage === 'getcredits',
},
{
label: 'Rewards',
path: '/rewards',
active: currentPage === 'rewards',
},
{
label: 'My Transactions',
path: '/history',
active: currentPage === 'history',
},
];
const navLinks = {
primary: [
{
label: 'Explore',
path: '/discover',
active: currentPage === 'discover',
icon: 'Compass',
},
{
label: 'Subscriptions',
path: '/subscriptions',
active: currentPage === 'subscriptions',
icon: 'AtSign',
},
],
secondary: [
{
label: 'Wallet',
path: walletLink || '/wallet', // If they've never been to a wallet page, take them to the overview
active:
currentPage === 'wallet' ||
!!walletSubLinks.find(({ path }) => currentPage === path.slice(1)),
subLinks: walletSubLinks,
icon: 'CreditCard',
},
{
label: 'Publish',
path: '/publish',
active: currentPage === 'publish',
icon: 'UploadCloud',
},
{
label: 'Settings',
path: '/settings',
active: currentPage === 'settings',
icon: 'Settings',
},
{
label: 'Backup Wallet',
path: '/backup',
active: currentPage === 'backup',
icon: 'Save',
},
{
label: 'Help',
path: '/help',
active: currentPage === 'help',
icon: 'HelpCircle',
},
],
};
return navLinks;
}
);

View file

@ -96,26 +96,6 @@ export const selectGettingNewAddress = createSelector(
state => state.gettingNewAddress state => state.gettingNewAddress
); );
export const selectDraftTransaction = createSelector(
selectState,
state => state.draftTransaction || {}
);
export const selectDraftTransactionAmount = createSelector(
selectDraftTransaction,
draft => draft.amount
);
export const selectDraftTransactionAddress = createSelector(
selectDraftTransaction,
draft => draft.address
);
export const selectDraftTransactionError = createSelector(
selectDraftTransaction,
draft => draft.error
);
export const selectBlocks = createSelector(selectState, state => state.blocks); export const selectBlocks = createSelector(selectState, state => state.blocks);
export const makeSelectBlockDate = block => export const makeSelectBlockDate = block =>

View file

@ -1,18 +1,25 @@
// Generic html styles used accross the App // Generic html styles used accross the App
// component specific styling should go in the component scss file // component specific styling should go in the component scss file
// The actual fonts used will change ex: medium vs regular
@font-face { @font-face {
font-family: 'Metropolis'; font-family: 'Metropolis';
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
src: url('../../../static/font/metropolis/Metropolis-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'Metropolis';
font-weight: 600;
font-style: normal;
text-rendering: optimizeLegibility;
src: url('../../../static/font/metropolis/Metropolis-Medium.woff2') format('woff2'); src: url('../../../static/font/metropolis/Metropolis-Medium.woff2') format('woff2');
} }
@font-face { @font-face {
font-family: 'Metropolis'; font-family: 'Metropolis';
font-weight: 600; font-weight: 700;
font-style: normal; font-style: normal;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
src: url('../../../static/font/metropolis/Metropolis-SemiBold.woff2') format('woff2'); src: url('../../../static/font/metropolis/Metropolis-SemiBold.woff2') format('woff2');
@ -63,19 +70,6 @@ h5 {
font-size: 1.1em; font-size: 1.1em;
} }
sup,
sub {
vertical-align: baseline;
position: relative;
}
sup {
top: -0.4em;
}
sub {
top: 0.4em;
}
code { code {
font: 0.8em Consolas, 'Lucida Console', 'Source Sans', monospace; font: 0.8em Consolas, 'Lucida Console', 'Source Sans', monospace;
background-color: var(--color-bg-alt); background-color: var(--color-bg-alt);
@ -83,35 +77,61 @@ code {
// Without this buttons don't have the Metropolis font // Without this buttons don't have the Metropolis font
button { button {
font-weight: inherit;
font-family: inherit; font-family: inherit;
} }
#window { ul {
height: 100%; list-style-type: none;
overflow: hidden;
} }
#main-content { input {
height: 100%; width: 100%;
overflow-y: auto; cursor: text;
position: absolute; border-bottom: var(--input-border-size) solid var(--input-border-color);
left: 0px; color: var(--input-color);
right: 0px; line-height: 1;
// don't use {bottom/top} here
// they cause flashes of un-rendered content when scrolling &.input-copyable {
margin-top: var(--header-height); background: var(--input-bg);
// TODO: fix this scrollbar extends beyond screen at the bottom color: var(--input-disabled-color);
padding-bottom: var(--header-height); width: 100%;
font-family: 'Consolas', 'Lucida Console', 'Adobe Source Code Pro', monospace;
border-bottom: 1px dotted var(--color-divider);
}
}
/*
*/
.page {
display: grid;
grid-template-rows: var(--header-height) calc(100vh - var(--header-height));
grid-template-columns: 170px auto;
grid-template-areas:
'nav header'
'nav content';
background-color: var(--color-bg); background-color: var(--color-bg);
} }
/*
Page content
*/
.content {
grid-area: content;
overflow: auto;
}
.main { .main {
padding: 0 $spacing-vertical * 2/3; padding: 0 $spacing-vertical $spacing-vertical;
max-width: 900px;
margin: auto;
} }
.main--no-padding { .main--no-padding {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
margin: 0;
max-width: none;
} }
.page__header { .page__header {
@ -124,26 +144,66 @@ button {
font-size: 3em; font-size: 3em;
} }
.columns {
display: flex;
justify-content: space-between;
> * {
flex-grow: 1;
flex-basis: 0;
&:not(:first-of-type):not(:last-of-type) {
margin: 0 $spacing-vertical / 3;
}
&:first-of-type {
margin-right: $spacing-vertical / 3;
}
&:last-of-type {
margin-left: $spacing-vertical / 3;
}
}
}
/* Custom text selection */ /* Custom text selection */
*::selection { *::selection {
background: var(--text-selection-bg); background: var(--text-selection-bg);
color: var(--text-selection-color); color: var(--text-selection-color);
} }
.credit-amount--indicator { .credit-amount {
font-weight: 500; padding: 5px;
color: var(--color-money); border-radius: 5px;
}
.credit-amount--fee {
font-size: 0.9em;
color: var(--color-meta-light);
}
.credit-amount--bold {
font-weight: 700; font-weight: 700;
font-size: 0.7em;
} }
.credit-amount--free {
color: var(--color-black);
background-color: var(--color-secondary);
}
.credit-amount--cost {
color: var(--color-black);
background-color: var(--color-third);
}
.credit-amount--large {
font-size: 2em;
font-weight: 800;
}
//
// .credit-amount--fee {
// font-size: 0.9em;
// color: var(--color-meta-light);
// }
//
// .credit-amount--bold {
// font-weight: 700;
// }
.hidden { .hidden {
display: none; display: none;
} }

File diff suppressed because it is too large Load diff

View file

@ -3,33 +3,39 @@ Both of these should probably die and become variables as well
*/ */
$spacing-vertical: 24px; $spacing-vertical: 24px;
$width-page-constrained: 800px; $width-page-constrained: 800px;
$text-color: #000;
:root { :root {
--spacing-vertical: 24px;
/* Colors */ /* Colors */
--color-white: #fff;
--color-black: #000; // I shouldn't use color names like this
--color-grey: #d6d6d6;
--color-grey-dark: #8e8e8e;
--color-primary: #44b098;
--color-primary-dark: #2c6e60;
--color-secondary: #6afbda;
--color-third: #fbd55e;
--color-divider: #e3e3e3;
--text-color: var(--color-black);
--color-brand: #155b4a; --color-brand: #155b4a;
--color-primary: #155b4a; // --color-dark-overlay: rgba(32, 32, 32, 0.9);
--color-primary-light: saturate(lighten(#155b4a, 50%), 20%);
--color-light-alt: hsl(hue(#155b4a), 15, 85);
--color-dark-overlay: rgba(32, 32, 32, 0.9);
--color-help: rgba(0, 0, 0, 0.54); --color-help: rgba(0, 0, 0, 0.54);
--color-notice: #8a6d3b; // --color-notice: #8a6d3b;
--color-error: #a94442; --color-error: #a94442;
--color-load-screen-text: #c3c3c3; // --color-load-screen-text: #c3c3c3;
--color-meta-light: #505050; // --color-meta-light: #505050;
--color-money: #216c2a; // --color-money: #216c2a;
--color-download: rgba(0, 0, 0, 0.75); // --color-download: rgba(0, 0, 0, 0.75);
--color-canvas: #f5f5f5; // --color-canvas: #f5f5f5;
--color-bg: #ffffff; --color-bg: #fafafa;
--color-bg-alt: #f6f6f6; --color-bg-alt: #f6f6f6;
--color-placeholder: #ececec; --color-placeholder: #ececec;
--color-nav-bg: #f6f6f6;
/* Misc */ /* Misc */
--content-max-width: 1000px; // --content-max-width: 1000px;
--nsfw-blur-intensity: 20px; // --nsfw-blur-intensity: 20px;
--height-video-embedded: $width-page-constrained * 9 / 16; // --height-video-embedded: $width-page-constrained * 9 / 16;
/* Font */ /* Font */
--font-size: 16px; --font-size: 16px;
@ -37,16 +43,10 @@ $text-color: #000;
--font-size-subtext-multiple: 0.82; --font-size-subtext-multiple: 0.82;
/* Shadows */ /* Shadows */
--box-shadow-layer: 0px 1px 3px 0px rgba(0, 0, 0, 0.2); // --box-shadow-layer: 0px 1px 3px 0px rgba(0, 0, 0, 0.2);
--box-shadow-focus: 2px 4px 4px 0 rgba(0, 0, 0, 0.14), 2px 5px 3px -2px rgba(0, 0, 0, 0.2), --box-shadow-layer: 0 4px 9px -2px var(--color-grey);
2px 3px 7px 0 rgba(0, 0, 0, 0.12);
/* Transition */
--transition-duration: 0.225s;
--transition-type: ease;
/* Text */ /* Text */
--text-color: $text-color;
--text-help-color: #eee; --text-help-color: #eee;
--text-max-width: 660px; --text-max-width: 660px;
--text-link-padding: 4px; --text-link-padding: 4px;
@ -58,10 +58,10 @@ $text-color: #000;
/* Input */ /* Input */
--input-bg: transparent; --input-bg: transparent;
--input-width: 330px; --input-label-color: var(--color-grey-dark);
--input-color: var(--text-color); --input-color: var(--text-color);
--input-border-size: 2px; --input-border-size: 1px;
--input-border-color: rgba(0, 0, 0, 0.54); --input-border-color: var(--color-grey-dark);
/* input:active */ /* input:active */
--input-active-bg: transparent; --input-active-bg: transparent;
@ -81,39 +81,28 @@ $text-color: #000;
--select-color: var(--text-color); --select-color: var(--text-color);
--select-height: 30px; --select-height: 30px;
//TODO: determine proper button variables;
/* Button */ /* Button */
--btn-primary-color: #fff; --btn-primary-color: #fff;
--button-alt-color: var(--text-color); --button-alt-color: var(--text-color);
--btn-primary-bg: var(--color-primary); --btn-primary-bg: var(--color-primary-dark);
--btn-alt-bg: red; --btn-inverse-color: var(--color-primary-dark);
--btn-radius: 10px; --btn-inverse-bg: var(--color-white);
// below needed? --btn-radius: 20px;
--btn-padding: $spacing-vertical * 2/3; --btn-height: 40px;
--btn-height: $spacing-vertical * 1.5;
--btn-intra-margin: $spacing-vertical;
/* Header */ /* Header */
--header-bg: var(--color-bg); --header-bg: var(--color-white);
--header-color: #666; --header-color: var(--color-text);
--header-active-color: rgba(0, 0, 0, 0.85); --header-active-color: rgba(0, 0, 0, 0.85);
--header-height: 65px; --header-height: $spacing-vertical * 3;
--header-button-bg: transparent; //var(--button-bg); --header-button-bg: transparent; //var(--button-bg);
--header-button-hover-bg: rgba(100, 100, 100, 0.15); --header-button-hover-bg: rgba(100, 100, 100, 0.15);
/* Header -> search */ /* Header -> search */
--search-bg: rgba(255, 255, 255, 0.7);
--search-border: 1px solid #ccc;
--search-color: #666; --search-color: #666;
--search-bg-color: #fff;
--search-active-color: var(--header-active-color); --search-active-color: var(--header-active-color);
--search-active-shadow: 0 0 3px 0px var(--text-selection-bg); --search-active-shadow: 0 6px 9px -2px var(--color-grey--dark);
/* Tabs */
--tab-bg: transparent;
--tab-color: rgba(0, 0, 0, 0.5);
--tab-active-color: var(--color-primary);
--tab-border-size: 2px;
--tab-border: var(--tab-border-size) solid var(--tab-active-color);
/* Table */ /* Table */
--table-border: 1px solid #e2e2e2; --table-border: 1px solid #e2e2e2;
@ -121,13 +110,10 @@ $text-color: #000;
--table-item-odd: #f4f4f4; --table-item-odd: #f4f4f4;
/* Card */ /* Card */
--card-bg: var(--color-bg);
--card-hover-translate: 10px; --card-hover-translate: 10px;
--card-margin: $spacing-vertical * 2/3; --card-margin: $spacing-vertical * 2/3;
--card-max-width: $width-page-constrained; --card-max-width: $width-page-constrained;
--card-padding: $spacing-vertical * 2/3;
--card-radius: 2px; --card-radius: 2px;
--card-link-scaling: 1.1;
--card-small-width: $spacing-vertical * 10; --card-small-width: $spacing-vertical * 10;
/* Modal */ /* Modal */
@ -136,12 +122,7 @@ $text-color: #000;
--modal-overlay-bg: rgba(#f5f5f5, 0.75); // --color-canvas: #F5F5F5 --modal-overlay-bg: rgba(#f5f5f5, 0.75); // --color-canvas: #F5F5F5
--modal-border: 1px solid rgb(204, 204, 204); --modal-border: 1px solid rgb(204, 204, 204);
/* Menu */ // /* Tooltip */
--menu-bg: var(--color-bg);
--menu-radius: 2px;
--menu-item-hover-bg: var(--color-bg-alt);
/* Tooltip */
--tooltip-width: 300px; --tooltip-width: 300px;
--tooltip-bg: var(--color-bg); --tooltip-bg: var(--color-bg);
--tooltip-color: var(--text-color); --tooltip-color: var(--text-color);
@ -153,10 +134,10 @@ $text-color: #000;
--scrollbar-thumb-active-bg: var(--color-primary); --scrollbar-thumb-active-bg: var(--color-primary);
--scrollbar-track-bg: transparent; --scrollbar-track-bg: transparent;
/* Divider */ // /* Divider */
--divider: 1px solid rgba(0, 0, 0, 0.12); // --divider: 1px solid rgba(0, 0, 0, 0.12);
//
/* Animation :) */ // /* Animation :) */
--animation-duration: 0.3s; // --animation-duration: 0.3s;
--animation-style: cubic-bezier(0.55, 0, 0.1, 1); // --animation-style: cubic-bezier(0.55, 0, 0.1, 1);
} }

View file

@ -1,7 +1,6 @@
@charset "UTF-8"; @charset "UTF-8";
@import '_reset'; @import '_reset';
@import '_vars'; @import '_vars';
@import '_icons';
@import '_gui'; @import '_gui';
@import 'component/_table'; @import 'component/_table';
@import 'component/_button.scss'; @import 'component/_button.scss';
@ -28,4 +27,5 @@
@import 'component/_radio.scss'; @import 'component/_radio.scss';
@import 'component/_shapeshift.scss'; @import 'component/_shapeshift.scss';
@import 'component/_spinner.scss'; @import 'component/_spinner.scss';
@import 'component/_nav.scss';
@import 'page/_show.scss'; @import 'page/_show.scss';

View file

@ -3,34 +3,35 @@ TODO:
Determine [disabled] or .disabled Determine [disabled] or .disabled
Add <a> support (probably just get rid of button prefix) Add <a> support (probably just get rid of button prefix)
*/ */
.btn {
button {
border: none; border: none;
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
} font-weight: 600;
button:disabled.btn--disabled {
cursor: default;
background-color: transparent;
}
button.btn {
padding: 10px; padding: 10px;
margin: 0 5px; height: var(--btn-height);
min-width: var(--btn-height);
border-radius: var(--btn-radius); border-radius: var(--btn-radius);
color: var(--btn-primary-color); color: var(--btn-primary-color);
background-color: var(--btn-primary-bg); background-color: var(--btn-primary-bg);
display: flex;
align-items: center;
justify-content: center;
fill: currentColor; // for proper icon color
&:hover:not(.btn--disabled) { &:hover {
box-shadow: var(--box-shadow-layer); box-shadow: var(--box-shadow-layer);
} }
.icon + .btn__label {
padding-left: 5px;
}
} }
button.btn.btn--alt { .btn.btn--alt {
color: var(--btn-alt-color); color: var(--btn-alt-color);
background-color: #efefef; background-color: var(--color-white);
&:hover { &:hover {
color: #111; color: #111;
@ -46,30 +47,71 @@ button.btn.btn--alt {
} }
} }
button.btn.btn--circle { .btn.btn--inverse {
border-radius: 50%; background-color: transparent;
transition: all 0.2s; color: var(--btn-inverse-color);
&:hover:not([disabled]) { &:hover {
border-radius: var(--btn-radius); background-color: var(--btn-inverse-bg);
} }
} }
button.btn.btn--inverse { .btn.btn--link {
box-shadow: none;
background-color: transparent;
color: var(--btn-primary-bg);
}
button.btn--link {
padding: 0; padding: 0;
margin: 0; margin: 0;
background-color: inherit; background-color: inherit;
font-size: 0.9em; font-size: 1em;
color: var(--btn-primary-bg); // this should be a different color color: var(--btn-inverse-color);
border-radius: 0;
display: inline;
&:hover { &:hover {
border-bottom: 1px solid; border-bottom: 1px solid;
box-shadow: none;
}
}
.btn--no-style {
font-size: inherit;
font-weight: inherit;
color: inherit;
background-color: inherit;
border-radius: 0;
padding: 5px 0;
margin: 0;
&:hover {
box-shadow: none;
color: var(--color-primary);
}
}
.btn.btn--link.btn--no-underline:hover {
border-bottom: none;
}
.btn--link,
.btn--no-style {
height: auto;
.btn__label {
padding: 0;
}
}
.btn.btn--disabled:disabled {
cursor: default;
&.btn--primary {
background-color: rgba(0, 0, 0, 0.5);
}
&.btn--alt {
// Come back to me
}
&:hover {
box-shadow: none;
} }
} }

View file

@ -1,20 +1,23 @@
.card { .card {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: var(--card-max-width);
border-radius: var(--card-radius); border-radius: var(--card-radius);
overflow: auto; overflow: auto;
user-select: text; user-select: text;
display: flex; display: flex;
position: relative;
} }
.card--placeholder { .card--section {
background-color: black; flex-direction: column;
background-color: var(--color-white);
padding: $spacing-vertical;
margin-top: $spacing-vertical * 2/3;
box-shadow: var(--box-shadow-layer);
} }
.card--small { .card--small {
width: var(--card-small-width); width: var(--card-small-width);
min-height: var(--card-small-width);
overflow-x: hidden; overflow-x: hidden;
white-space: normal; white-space: normal;
} }
@ -48,7 +51,8 @@
margin-top: $spacing-vertical * 1/3; margin-top: $spacing-vertical * 1/3;
} }
// TODO: regular .card__title for show page // TODO: regular .card__title
// maybe not needed?
.card__title--small { .card__title--small {
font-weight: 600; font-weight: 600;
font-size: 0.9em; font-size: 0.9em;
@ -60,145 +64,24 @@
padding-top: $spacing-vertical * 1/3; padding-top: $spacing-vertical * 1/3;
} }
// .card__title-primary .meta { .card-media__internal-links {
// white-space: nowrap; position: absolute;
// overflow: hidden; top: 5px;
// text-overflow: ellipsis; right: 5px;
// } }
//
// .card__content {
// .card__actions { margin-top: var(--card-margin);
// margin-top: var(--card-margin); margin-bottom: var(--card-margin);
// margin-bottom: var(--card-margin); }
// user-select: none;
// }
//
// .card__actions--bottom {
// margin-top: $spacing-vertical * 1/3;
// margin-bottom: $spacing-vertical * 1/3;
// border-top: var(--divider);
// }
//
// .card__actions--form-submit {
// margin-top: $spacing-vertical;
// margin-bottom: var(--card-margin);
// }
//
// .card__action--right {
// float: right;
// }
//
// .card__content {
// margin-top: var(--card-margin);
// margin-bottom: var(--card-margin);
// table:not(:last-child) {
// margin-bottom: var(--card-margin);
// }
// }
//
// .card__actions--only-vertical {
// margin-left: 0;
// margin-right: 0;
// padding-left: 0;
// padding-right: 0;
// }
//
// .card__content--extra-vertical-space {
// margin: $spacing-vertical 0;
// }
//
// $font-size-subtext-multiple: 0.82;
// .card__subtext {
// color: var(--color-meta-light);
// font-size: calc(var(--font-size-subtext-multiple) * 1em);
// margin-top: $spacing-vertical * 1/3;
// margin-bottom: $spacing-vertical * 1/3;
// }
// .card__subtext--allow-newlines {
// white-space: pre-wrap;
// }
// .card__subtext--two-lines {
// height: calc(
// var(--font-size) * var(--font-size-subtext-multiple) * var(--font-line-height) * 2
// ); /*this is so one line text still has the proper height*/
// }
// .card-overlay {
// position: absolute;
// left: 0px;
// right: 0px;
// top: 0px;
// bottom: 0px;
// padding: 20px;
// background-color: var(--color-dark-overlay);
// color: #fff;
// display: flex;
// align-items: center;
// font-weight: 600;
// }
//
//
// .card__media--autothumb {
// position: relative;
// }
// .card__media--autothumb.purple {
// background-color: #9c27b0;
// }
// .card__media--autothumb.red {
// background-color: #e53935;
// }
// .card__media--autothumb.pink {
// background-color: #e91e63;
// }
// .card__media--autothumb.indigo {
// background-color: #3f51b5;
// }
// .card__media--autothumb.blue {
// background-color: #2196f3;
// }
// .card__media--autothumb.light-blue {
// background-color: #039be5;
// }
// .card__media--autothumb.cyan {
// background-color: #00acc1;
// }
// .card__media--autothumb.teal {
// background-color: #009688;
// }
// .card__media--autothumb.green {
// background-color: #43a047;
// }
// .card__media--autothumb.yellow {
// background-color: #ffeb3b;
// }
// .card__media--autothumb.orange {
// background-color: #ffa726;
// }
//
// .card__media--autothumb .card__autothumb__text {
// font-size: 2em;
// width: 100%;
// color: #ffffff;
// text-align: center;
// position: absolute;
// top: 36%;
// }
//
// .card--form {
// width: calc(var(--input-width) + var(--card-padding) * 2);
// }
//
// .card__actions {
// .card-series-submit { margin-top: var(--card-margin);
// margin-left: auto; display: flex;
// margin-right: auto; }
// max-width: var(--card-max-width);
// padding: $spacing-vertical / 2;
// }
/* /*
.card-row is used on the discover page .card-row is used on the discover/subscriptions page
It is a list of cards that extend past the right edge of the screen It is a list of cards that extend past the right edge of the screen
There are left/right arrows to scroll the cards and view hidden content There are left/right arrows to scroll the cards and view hidden content
*/ */
@ -208,6 +91,14 @@
width: 100%; width: 100%;
min-width: var(--card-small-width); min-width: var(--card-small-width);
padding-top: $spacing-vertical; padding-top: $spacing-vertical;
&:first-of-type {
padding-top: $spacing-vertical * 2/3;
}
&:last-of-type {
padding-bottom: $spacing-vertical * 2/3;
}
} }
.card-row__header { .card-row__header {
@ -225,6 +116,10 @@
align-items: center; align-items: center;
} }
.card-row__scroll-btns {
display: flex;
}
.card-row__scrollhouse { .card-row__scrollhouse {
padding-top: $spacing-vertical * 2/3; padding-top: $spacing-vertical * 2/3;
overflow: hidden; overflow: hidden;
@ -233,26 +128,10 @@
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
margin-left: $spacing-vertical * 2/3; margin-left: $spacing-vertical * 2/3;
overflow: visible;
} }
.card:last-of-type { .card:last-of-type {
padding-right: $spacing-vertical * 2/3; margin-right: $spacing-vertical * 2/3;
} }
} }
/*
if we keep doing things like this, we should add a real grid system, but I'm going to be a selective dick about it - Jeremy
*/
//TODO: css grid
// .card-grid {
// $margin-card-grid: $spacing-vertical * 2/3;
// display: flex;
// flex-wrap: wrap;
// > .card {
// width: $width-page-constrained / 2 - $margin-card-grid / 2;
// flex-grow: 1;
// }
// > .card:nth-of-type(2n - 1):not(:last-child) {
// margin-right: $margin-card-grid;
// }
// }

View file

@ -1,195 +1,40 @@
.form-row-submit { .form-row {
margin-top: $spacing-vertical;
}
.form-row-submit--with-footer {
margin-bottom: $spacing-vertical;
}
.form-row-phone {
display: flex; display: flex;
flex-direction: row;
.form-field__input-text { .form-field:not(:first-of-type) {
margin-left: 5px; padding-left: $spacing-vertical;
width: calc(0.85 * var(--input-width));
} }
} }
.form-row__label-row { .form-field__wrapper {
margin-top: $spacing-vertical * 5/6; display: flex;
margin-bottom: 0px; padding: $spacing-vertical / 3 0;
line-height: 1;
font-size: calc(0.9 * var(--font-size));
}
.form-row__label-row--prefix {
float: left;
margin-right: 5px;
}
.form-row--focus {
.form-field__label,
.form-field__prefix {
color: var(--color-primary) !important;
}
}
.form-field {
display: inline-block;
margin: 8px 0;
select {
transition: outline var(--transition-duration) var(--transition-type);
box-sizing: border-box;
padding-left: 5px;
padding-right: 5px;
height: var(--select-height);
background: var(--select-bg);
color: var(--select-color);
&:focus {
outline: var(--input-border-size) solid var(--color-primary);
}
}
input[type='radio'],
input[type='checkbox'] {
&:checked + .form-field__label {
color: var(--text-color);
}
}
input[type='text'].input-copyable {
background: var(--input-bg);
color: var(--input-disabled-color);
line-height: 1;
padding-top: $spacing-vertical * 1/3;
padding-bottom: $spacing-vertical * 1/3;
padding-left: 5px;
padding-right: 5px;
width: 100%;
font-family: 'Consolas', 'Lucida Console', 'Adobe Source Code Pro', monospace;
&.input-copyable--with-copy-btn {
width: 85%;
}
}
input[readonly] {
color: var(--input-disabled-color) !important;
border-bottom: 1px dashed var(--input-disabled-border-color) !important;
}
input[readonly]:focus {
background: var(--input-bg) !important;
border-bottom: 1px dashed var(--input-disabled-border-color) !important;
}
textarea,
input[type='text'],
input[type='password'],
input[type='email'],
input[type='number'],
input[type='search'],
input[type='date'] {
background: var(--input-bg);
border-bottom: var(--input-border-size) solid var(--input-border-color);
caret-color: var(--color-primary);
color: var(--input-color);
cursor: pointer;
line-height: 1;
padding: 0 1px 8px 1px;
box-sizing: border-box;
-webkit-appearance: none;
transition: all var(--transition-duration) var(--transition-type);
&::-webkit-input-placeholder {
color: var(--input-placeholder-color);
opacity: var(--input-placeholder-opacity) !important;
}
&:focus {
border-color: var(--color-primary);
background: var(--input-active-bg);
}
&:hover:not(:focus) {
border-color: var(--input-hover-border-color);
}
&.form-field__input--error {
border-color: var(--color-error);
}
&.form-field__input--inline {
padding-top: 0;
padding-bottom: 0;
border-bottom-width: var(--input-border-size);
margin-left: 8px;
margin-right: 8px;
}
}
textarea {
padding: 2px;
border: var(--input-border-size) solid var(--input-border-color);
}
}
.form-field--address {
width: 100%;
}
.form-field--SimpleMDE {
display: block;
}
.form-field__label,
.form-row__label {
color: var(--form-label-color);
&[for] {
cursor: pointer;
}
}
.form-row__label-row .form-field__label--error {
/*the row restriction is to prevent coloring checkboxes and radio labels*/
color: var(--color-error);
}
.form-field__input-text {
width: var(--input-width);
}
.form-field__prefix {
margin-right: 4px;
}
.form-field__postfix {
margin-left: 4px;
}
.form-field__input-number {
width: 70px;
text-align: right;
}
.form-field--textarea {
width: 100%;
}
.form-field__input-textarea {
width: 100%;
}
.form-field__error,
.form-field__helper {
margin-top: $spacing-vertical * 1/3;
font-size: 0.8em;
transition: opacity var(--transition-duration) var(--transition-type);
} }
.form-field__error { .form-field__error {
color: var(--color-error); color: var(--color-error);
} }
.form-field__helper {
color: var(--color-help); .form-field__label {
color: var(--color-grey-dark);
} }
.form-field__input.form-field__input-SimpleMDE .CodeMirror-scroll { .form-field__prefix {
height: auto; padding-right: 5px;
}
.form-field__postfix {
padding-left: 5px;
}
// Not sure if I like these
// Maybe this should be in gui.scss?
.input--lbc-amount {
width: 75px;
font-weight: 700;
}
.input--address {
width: 370px;
} }

View file

@ -1,60 +1,86 @@
#header { .header {
grid-area: header;
display: flex;
display: flex; display: flex;
align-items: center; align-items: center;
position: fixed; justify-content: space-between;
top: 0; padding: 0 $spacing-vertical;
left: 0; align-items: center;
width: 100%; justify-content: space-between;
height: var(--header-height); height: 100%;
z-index: 3; background-color: var(--color-bg);
box-sizing: border-box;
color: var(--header-color);
background-color: var(--header-bg);
}
.header__actions-left {
display: flex;
padding: 0 5px;
} }
.header__actions-right { .header__actions-right {
margin-left: auto; margin-left: auto;
padding-left: $spacing-vertical / 2;
display: flex;
} }
.header__wunderbar { .header__wunderbar {
z-index: 1;
flex: 1; flex: 1;
max-width: 325px;
min-width: 175px; min-width: 175px;
overflow: hidden; overflow: visible;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
height: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 10px 5px;
cursor: text; cursor: text;
} position: relative;
.wunderbar__input { .icon {
height: 50%; position: absolute;
width: 100%; left: 10px;
color: var(--search-color);
padding: 10px;
background-color: #f3f3f3;
border-radius: 10px;
font-size: 0.9em;
&:focus {
// TODO: focus style
} }
} }
.wunderbar__input {
height: var(--btn-height);
border-radius: var(--btn-radius);
width: 100%;
max-width: 700px;
color: var(--search-color);
background-color: var(--search-bg-color);
box-shadow: var(--box-shadow-layer);
padding: 10px;
padding-left: 30px;
font-size: 0.9em;
display: flex;
align-items: center;
justify-content: center;
border-bottom: none;
&:focus {
background-color: var(--color-bg);
border-radius: 0;
border-bottom: 1px solid var(--color-grey);
box-shadow: none;
}
}
.wunderbar__menu {
max-width: 100px;
overflow-x: hidden;
}
.wunderbar__suggestion { .wunderbar__suggestion {
padding: 5px; padding: 10px;
background-color: var(--header-bg); background-color: var(--header-bg);
cursor: pointer; cursor: pointer;
display: flex;
align-items: center;
text-overflow: ellipsis;
&:not(:first-of-type) {
border-top: 1px solid var(--color-divider);
}
}
.wunderbar__suggestion-label {
padding-left: $spacing-vertical;
} }
.wunderbar__active-suggestion { .wunderbar__active-suggestion {
background-color: #a3ffb0; background-color: var(--color-secondary);
} }

View file

@ -0,0 +1,51 @@
.nav {
grid-area: nav;
background-color: var(--color-nav-bg);
padding-top: 16px;
hr {
width: 40px;
border: solid 1px var(--color-grey);
margin: $spacing-vertical $spacing-vertical * 2/3 $spacing-vertical * 2;
}
}
.nav__actions-top {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px;
}
.nav__actions-history {
display: flex;
}
.nav__primary {
padding-top: $spacing-vertical * 3;
}
.nav__link {
padding: $spacing-vertical / 3 0 $spacing-vertical / 3 $spacing-vertical * 2/3;
font-weight: bold;
color: var(--color-grey-dark);
// The hover effect should be on the li
// Need to have the button grow
& .btn:hover {
color: var(--color-black);
}
}
.nav__link--active {
color: var(--color-black);
}
.nav__sub {
padding-top: 5px;
}
.nav__sub-link {
padding: 5px $spacing-vertical * 2/3;
font-size: 0.8em;
}

View file

@ -1,17 +1,3 @@
// Can't think of a better way to do this
// The initial shapeshift form is 311px tall
// the .shapeshift__initial-wrapper class is only added when the form is being loaded
// Once the form is rendered, there is a very smooth transition because the height doesn't change
.shapeshift__wrapper.shapeshift__initial-wrapper {
min-height: 346px;
}
.shapeshift__content {
.spinner {
margin-top: $spacing-vertical * 3;
}
}
.shapeshift__tx-info { .shapeshift__tx-info {
min-height: 63px; min-height: 63px;
} }

View file

@ -0,0 +1,23 @@
// @flow
/* eslint-disable prefer-default-export */
import { REGEXP_ADDRESS } from 'lbryURI';
type DraftTxValues = {
address: string,
// amount: number
}
export const validateSendTx = (formValues: DraftTxValues) => {
const { address } = formValues
const errors = {};
// All we need to check is if the address is valid
// If values are missing, users wont' be able to submit the form
if (address && !REGEXP_ADDRESS.test(address)) {
errors.address = __('Not a valid LBRY address');
}
return errors;
};
/* eslint-enable prefer-default-export */

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

View file

@ -7486,6 +7486,10 @@ react-dom@^16.2.0:
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types "^15.6.0" prop-types "^15.6.0"
react-feather@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-1.0.8.tgz#69b13d5c729949f194d33201dee91bab67fa31a2"
react-markdown@^2.5.0: react-markdown@^2.5.0:
version "2.5.1" version "2.5.1"
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-2.5.1.tgz#f7a6c26a3a5faf5d4c2098155d9775e826fd56ee" resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-2.5.1.tgz#f7a6c26a3a5faf5d4c2098155d9775e826fd56ee"