[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='^page\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/page\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'
[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",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-feather": "^1.0.8",
"react-markdown": "^2.5.0",
"react-modal": "^3.1.7",
"react-paginate": "^5.0.0",

View file

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

View file

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

View file

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

View file

@ -200,7 +200,7 @@ class CategoryList extends React.PureComponent<Props, State> {
<h3>
{categoryLink ? (
<Button
className="button-text no-underline"
noStyle
label={category}
navigate="/show"
navigateParams={{ uri: categoryLink }}
@ -219,20 +219,20 @@ class CategoryList extends React.PureComponent<Props, State> {
/>
)}
</div>
<div>
<div className="card-row__scroll-btns">
<Button
inverse
circle
disabled={!canScrollPrevious}
onClick={this.handleScrollPrevious}
icon="chevron-left"
icon="ChevronLeft"
/>
<Button
inverse
circle
disabled={!canScrollNext}
onClick={this.handleScrollNext}
icon="chevron-right"
icon="ChevronRight"
/>
</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
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 = {
icon: string,
fixed?: boolean,
padded?: boolean,
size?: number,
};
class Icon extends React.PureComponent<Props> {
getIconTitle() {
const { icon } = this.props;
switch (icon) {
case icons.FEATURED:
return __('Watch this and earn rewards.');
case icons.LOCAL:
return __('You have a copy of this file.');
default:
return '';
}
}
class IconComponent extends React.PureComponent<Props> {
// TODO: Move all icons to constants and add titles for all
// Add some some sort of hover flyout with the title?
render() {
const { icon, fixed, padded } = this.props;
const iconClassName = icon.startsWith('icon-') ? icon : `icon-${icon}`;
const title = this.getIconTitle();
const spanClassName = classnames(
{
'icon--fixed-width': fixed,
'icon--padded': padded,
},
iconClassName
);
return <span className={spanClassName} title={title} />;
const { icon, size = 14 } = this.props;
const Icon = Icons[icon];
return Icon ? <Icon size={size} className="icon" /> : null;
}
}
export default Icon;
export default IconComponent;

View file

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

View file

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

View file

@ -1,35 +1,48 @@
// @flow
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() {
this.fetchCost(this.props);
}
componentWillReceiveProps(nextProps) {
componentWillReceiveProps(nextProps: Props) {
this.fetchCost(nextProps);
}
fetchCost(props) {
fetchCost = (props: Props) => {
const { costInfo, fetchCostInfo, uri, fetching, claim } = props;
if (costInfo === undefined && !fetching && claim) {
fetchCostInfo(uri);
}
}
};
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) {
return <span className={`credit-amount credit-amount--${look}`}>???</span>;
return <span className="credit-amount">???</span>;
}
return (
<CreditAmount
label={false}
amount={costInfo.cost}
isEstimate={isEstimate}
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 PropTypes from 'prop-types';
import FileSelector from 'component/file-selector.js';
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';
const formFieldFileSelectorTypes = ['file', 'directory'];
@ -195,3 +197,4 @@ class FormField extends React.PureComponent {
}
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 { selectIsBackDisabled, selectIsForwardDisabled } from 'redux/selectors/navigation';
import { selectBalance } from 'redux/selectors/wallet';
import { doNavigate, doHistoryBack, doHistoryForward } from 'redux/actions/navigation';
import Header from './view';
import { doNavigate } from 'redux/actions/navigation';
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 => ({
isBackDisabled: selectIsBackDisabled(state),
isForwardDisabled: selectIsForwardDisabled(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state),
balance: formatCredits(selectBalance(state) || 0, 2),
});
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
back: () => dispatch(doHistoryBack()),
forward: () => dispatch(doHistoryForward()),
downloadUpgrade: () => dispatch(doDownloadUpgrade()),
});
export default connect(select, perform)(Header);

View file

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

View file

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

View file

@ -22,6 +22,8 @@ type Props = {
alt: ?boolean,
flat: ?boolean,
fakeLink: ?boolean,
noStyle: ?boolean,
noUnderline: ?boolean,
description: ?string,
};
@ -45,20 +47,25 @@ const Button = (props: Props) => {
flat,
fakeLink,
description,
noStyle,
noUnderline,
...otherProps
} = props;
const combinedClassName = classnames(
{
btn: !fakeLink,
'btn--link': fakeLink,
'btn--primary': !fakeLink && !alt,
'btn--alt': alt,
'btn--inverse': inverse,
'btn--disabled': disabled,
'btn--circle': circle,
'btn--flat': flat,
},
'btn',
noStyle
? 'btn--no-style'
: {
'btn--link': fakeLink,
'btn--primary': !alt && !fakeLink,
'btn--alt': alt,
'btn--inverse': inverse,
'btn--disabled': disabled,
'btn--circle': circle,
'btn--flat': flat,
'btn--no-underline': fakeLink && noUnderline,
},
className
);
@ -72,10 +79,10 @@ const Button = (props: Props) => {
const content = (
<React.Fragment>
{icon && <Icon icon={icon} fixed />}
{icon && <Icon icon={icon} />}
{label && <span className="btn__label">{label}</span>}
{children && children}
{iconRight && <Icon icon={iconRight} fixed />}
{iconRight && <Icon icon={iconRight} />}
</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 { 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';
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
import * as React from 'react';
import classnames from 'classnames';
import { BusyMessage } from 'component/common';
type Props = {
children: React.Node,
title: ?string,
pageTitle: ?string,
noPadding: ?boolean,
isLoading: ?boolean,
};
const Page = (props: Props) => {
const { children, title, noPadding, isLoading } = props;
const { pageTitle, children, noPadding } = props;
return (
<main id="main-content">
<div className="page__header">
{title && <h1 className="page__title">{title}</h1>}
{isLoading && <BusyMessage message={__('Fetching content')} />}
</div>
<div className={classnames('main', { 'main--no-padding': noPadding })}>{children}</div>
<main className={classnames('main', { 'main--no-padding': noPadding })}>
{pageTitle && (
<div className="page__header">
{pageTitle && <h1 className="page__title">{pageTitle}</h1>}
</div>
)}
{children}
</main>
);
};

View file

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

View file

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

View file

@ -1,7 +1,20 @@
// @flow
import React from 'react';
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;
if (!rewards || !rewards.length) {
@ -9,7 +22,7 @@ const RewardListClaimed = props => {
}
return (
<section className="card">
<section className="card card--section">
<div className="card__title-identity">
<h3>Claimed Rewards</h3>
</div>

View file

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

View file

@ -1,16 +1,30 @@
// @flow
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 Link from 'component/link';
import Button from 'component/link';
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 claimed = !!reward.transaction_id;
return (
<section className="card">
<section className="card card--section">
<div className="card__inner">
<div className="card__title-primary">
<CreditAmount amount={reward.reward_amount} />
@ -18,8 +32,8 @@ const RewardTile = props => {
</div>
<div className="card__content">{reward.reward_description}</div>
<div className="card__actions ">
{reward.reward_type == rewards.TYPE_REFERRAL && (
<Link button="alt" navigate="/invite" label={__('Go To Invites')} />
{reward.reward_type === rewards.TYPE_REFERRAL && (
<Button alt navigate="/invite" label={__('Go To Invites')} />
)}
{reward.reward_type !== rewards.TYPE_REFERRAL &&
(claimed ? (

View file

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

View file

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

View file

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

View file

@ -1,10 +1,18 @@
// @flow
import React from 'react';
import { BusyMessage } from 'component/common';
import Link from 'component/link';
import Button from 'component/link';
import TransactionList from 'component/transactionList';
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() {
this.props.fetchTransactions();
}
@ -13,28 +21,20 @@ class TransactionListRecent extends React.PureComponent {
const { fetchingTransactions, hasTransactions, transactions } = this.props;
return (
<section className="card">
<div className="card__title-primary">
<h3>{__('Recent Transactions')}</h3>
</div>
<section className="card card--section">
<h2>{__('Recent Transactions')}</h2>
<div className="card__content">
{fetchingTransactions && <BusyMessage message={__('Loading transactions')} />}
{!fetchingTransactions && (
<TransactionList
transactions={transactions}
emptyMessage={__('You have no recent transactions.')}
emptyMessage={__("Looks like you don't have any recent transactions.")}
/>
)}
</div>
{hasTransactions && (
<div className="card__actions card__actions--bottom">
<Link
navigate="/history"
label={__('Full History')}
icon={icons.HISTORY}
className="no-underline"
button="text"
/>
<div className="card__actions">
<Button navigate="/history" label={__('Full History')} icon="Clock" />
</div>
)}
</section>

View file

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

View file

@ -1,5 +1,7 @@
// I'll come back to this
/* eslint-disable */
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 {
constructor(props) {
@ -53,3 +55,4 @@ class UserEmailNew extends React.PureComponent {
}
export default UserEmailNew;
/* eslint-enable */

View file

@ -1,6 +1,8 @@
// I'll come back to this
/* eslint-disable */
import React from 'react';
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 {
constructor(props) {
@ -29,19 +31,22 @@ class UserEmailVerify extends React.PureComponent {
render() {
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 (
<Form onSubmit={this.handleSubmit.bind(this)}>
<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 */}
<div className="form-field__helper">
<p>
@ -60,3 +65,4 @@ class UserEmailVerify extends React.PureComponent {
}
export default UserEmailVerify;
/* eslint-enable */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,8 @@
// I'll come back to this
/* eslint-disable */
import React from 'react';
import Link from 'component/link';
import { FormRow } from 'component/form';
import { FormRow } from 'component/common/form';
import UriIndicator from 'component/uriIndicator';
class WalletSendTip extends React.PureComponent {
@ -67,3 +69,4 @@ class WalletSendTip extends React.PureComponent {
}
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);
return React.cloneElement(menu, {
ref: e => (this.refs.menu = e),
className: 'wunderbar__menu',
// Ignore blur to prevent menu from de-rendering before we can process click
onMouseEnter: () => this.setIgnoreBlur(true),
onMouseLeave: () => this.setIgnoreBlur(false),

View file

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

View file

@ -37,8 +37,6 @@ export const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED';
export const UPDATE_BALANCE = 'UPDATE_BALANCE';
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 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_COMPLETED = 'SEND_TRANSACTION_COMPLETED';
export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED';

View file

@ -1,5 +1,5 @@
export const FEATURED = 'rocket';
export const LOCAL = 'folder';
export const FEATURED = 'Award';
export const LOCAL = 'Folder';
export const FILE = 'file';
export const HISTORY = 'history';
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 { 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';
const ModalCreditIntro = props => {
@ -46,3 +49,4 @@ const ModalCreditIntro = props => {
};
export default ModalCreditIntro;
/* esline-enable */

View file

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

View file

@ -4,6 +4,7 @@ import Link from 'component/link';
import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify';
import UserVerify from 'component/userVerify';
import Page from 'component/page';
export class AuthPage extends React.PureComponent {
componentWillMount() {
@ -58,25 +59,27 @@ export class AuthPage extends React.PureComponent {
const { email, user, isPending, navigate } = this.props;
const [innerContent, useTemplate] = this.renderMain();
return useTemplate ? (
<main>
<section className="card card--form">
<div className="card__title-primary">
<h1>{this.getTitle()}</h1>
</div>
<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')} />.
return (
<Page>
{useTemplate ? (
<section className="card card--section">
<div className="card__title-primary">
<h1>{this.getTitle()}</h1>
</div>
</div>
</section>
</main>
) : (
innerContent
<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>
</section>
) : (
innerContent
)}
</Page>
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import * as ACTIONS from 'constants/action_types';
import { normalizeURI } from 'lbryURI';
import { normalizeURI, buildURI } from 'lbryURI';
import { doResolveUri } from 'redux/actions/content';
import { doNavigate } from 'redux/actions/navigation';
import { selectCurrentPage } from 'redux/selectors/navigation';
@ -89,10 +89,17 @@ export const getSearchSuggestions = value => dispatch => {
fetch(`https://lighthouse.lbry.io/autocomplete?s=${searchValue}`)
.then(handleSearchApiResponse)
.then(suggestions => {
const formattedSuggestions = suggestions.slice(0, 5).map(suggestion => ({
label: suggestion,
value: suggestion,
}));
const formattedSuggestions = suggestions.slice(0, 5).map(suggestion => {
// This will need to be more robust when the api starts returning lbry uris
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?
// If it's not a valid uri, then add a "search for {query}" result
@ -100,12 +107,12 @@ export const getSearchSuggestions = value => dispatch => {
try {
const uri = normalizeURI(value);
formattedSuggestions.unshift(
{ label: uri, value: uri },
{ label: searchLabel, value: `${value}?search` }
{ label: uri, value: uri, icon: 'Compass' },
{ label: searchLabel, value: `${value}?search`, icon: 'Search' }
);
} catch (e) {
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 {
selectBalance,
selectDraftTransaction,
selectDraftTransactionAmount,
} from 'redux/selectors/wallet';
export function doUpdateBalance() {
@ -89,12 +87,10 @@ export function doCheckAddressIsMine(address) {
};
}
export function doSendDraftTransaction() {
export function doSendDraftTransaction({ amount, address }) {
return (dispatch, getState) => {
const state = getState();
const draftTx = selectDraftTransaction(state);
const balance = selectBalance(state);
const amount = selectDraftTransactionAmount(state);
if (balance - amount <= 0) {
dispatch(doOpenModal(MODALS.INSUFFICIENT_CREDITS));
@ -135,26 +131,12 @@ export function doSendDraftTransaction() {
};
Lbry.wallet_send({
amount: draftTx.amount,
address: draftTx.address,
amount,
address,
}).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) {
return (dispatch, getState) => {
const state = getState();

View file

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

View file

@ -1,6 +1,5 @@
import { createSelector } from 'reselect';
import { parseQueryParams, toQueryString } from 'util/query_params';
import { normalizeURI } from 'lbryURI';
import { parseQueryParams } from 'util/query_params';
export const selectState = state => state.navigation || {};
@ -22,72 +21,6 @@ export const selectCurrentParams = createSelector(selectCurrentPath, path => {
export const makeSelectCurrentParam = param =>
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 selectIsBackDisabled = createSelector(selectState, state => state.index === 0);
@ -97,6 +30,8 @@ export const selectIsForwardDisabled = createSelector(
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 selectHistoryStack = createSelector(selectState, state => state.stack);
@ -106,3 +41,126 @@ export const selectActiveHistoryEntry = createSelector(
selectState,
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
);
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 makeSelectBlockDate = block =>

View file

@ -1,18 +1,25 @@
// Generic html styles used accross the App
// component specific styling should go in the component scss file
// The actual fonts used will change ex: medium vs regular
@font-face {
font-family: 'Metropolis';
font-weight: normal;
font-style: normal;
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');
}
@font-face {
font-family: 'Metropolis';
font-weight: 600;
font-weight: 700;
font-style: normal;
text-rendering: optimizeLegibility;
src: url('../../../static/font/metropolis/Metropolis-SemiBold.woff2') format('woff2');
@ -63,19 +70,6 @@ h5 {
font-size: 1.1em;
}
sup,
sub {
vertical-align: baseline;
position: relative;
}
sup {
top: -0.4em;
}
sub {
top: 0.4em;
}
code {
font: 0.8em Consolas, 'Lucida Console', 'Source Sans', monospace;
background-color: var(--color-bg-alt);
@ -83,35 +77,61 @@ code {
// Without this buttons don't have the Metropolis font
button {
font-weight: inherit;
font-family: inherit;
}
#window {
height: 100%;
overflow: hidden;
ul {
list-style-type: none;
}
#main-content {
height: 100%;
overflow-y: auto;
position: absolute;
left: 0px;
right: 0px;
// don't use {bottom/top} here
// they cause flashes of un-rendered content when scrolling
margin-top: var(--header-height);
// TODO: fix this scrollbar extends beyond screen at the bottom
padding-bottom: var(--header-height);
input {
width: 100%;
cursor: text;
border-bottom: var(--input-border-size) solid var(--input-border-color);
color: var(--input-color);
line-height: 1;
&.input-copyable {
background: var(--input-bg);
color: var(--input-disabled-color);
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);
}
/*
Page content
*/
.content {
grid-area: content;
overflow: auto;
}
.main {
padding: 0 $spacing-vertical * 2/3;
padding: 0 $spacing-vertical $spacing-vertical;
max-width: 900px;
margin: auto;
}
.main--no-padding {
padding-left: 0;
padding-right: 0;
margin: 0;
max-width: none;
}
.page__header {
@ -124,26 +144,66 @@ button {
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 */
*::selection {
background: var(--text-selection-bg);
color: var(--text-selection-color);
}
.credit-amount--indicator {
font-weight: 500;
color: var(--color-money);
}
.credit-amount--fee {
font-size: 0.9em;
color: var(--color-meta-light);
}
.credit-amount--bold {
.credit-amount {
padding: 5px;
border-radius: 5px;
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 {
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;
$width-page-constrained: 800px;
$text-color: #000;
:root {
--spacing-vertical: 24px;
/* 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-primary: #155b4a;
--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-dark-overlay: rgba(32, 32, 32, 0.9);
--color-help: rgba(0, 0, 0, 0.54);
--color-notice: #8a6d3b;
// --color-notice: #8a6d3b;
--color-error: #a94442;
--color-load-screen-text: #c3c3c3;
--color-meta-light: #505050;
--color-money: #216c2a;
--color-download: rgba(0, 0, 0, 0.75);
--color-canvas: #f5f5f5;
--color-bg: #ffffff;
// --color-load-screen-text: #c3c3c3;
// --color-meta-light: #505050;
// --color-money: #216c2a;
// --color-download: rgba(0, 0, 0, 0.75);
// --color-canvas: #f5f5f5;
--color-bg: #fafafa;
--color-bg-alt: #f6f6f6;
--color-placeholder: #ececec;
--color-nav-bg: #f6f6f6;
/* Misc */
--content-max-width: 1000px;
--nsfw-blur-intensity: 20px;
--height-video-embedded: $width-page-constrained * 9 / 16;
// --content-max-width: 1000px;
// --nsfw-blur-intensity: 20px;
// --height-video-embedded: $width-page-constrained * 9 / 16;
/* Font */
--font-size: 16px;
@ -37,16 +43,10 @@ $text-color: #000;
--font-size-subtext-multiple: 0.82;
/* Shadows */
--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),
2px 3px 7px 0 rgba(0, 0, 0, 0.12);
/* Transition */
--transition-duration: 0.225s;
--transition-type: ease;
// --box-shadow-layer: 0px 1px 3px 0px rgba(0, 0, 0, 0.2);
--box-shadow-layer: 0 4px 9px -2px var(--color-grey);
/* Text */
--text-color: $text-color;
--text-help-color: #eee;
--text-max-width: 660px;
--text-link-padding: 4px;
@ -58,10 +58,10 @@ $text-color: #000;
/* Input */
--input-bg: transparent;
--input-width: 330px;
--input-label-color: var(--color-grey-dark);
--input-color: var(--text-color);
--input-border-size: 2px;
--input-border-color: rgba(0, 0, 0, 0.54);
--input-border-size: 1px;
--input-border-color: var(--color-grey-dark);
/* input:active */
--input-active-bg: transparent;
@ -81,39 +81,28 @@ $text-color: #000;
--select-color: var(--text-color);
--select-height: 30px;
//TODO: determine proper button variables;
/* Button */
--btn-primary-color: #fff;
--button-alt-color: var(--text-color);
--btn-primary-bg: var(--color-primary);
--btn-alt-bg: red;
--btn-radius: 10px;
// below needed?
--btn-padding: $spacing-vertical * 2/3;
--btn-height: $spacing-vertical * 1.5;
--btn-intra-margin: $spacing-vertical;
--btn-primary-bg: var(--color-primary-dark);
--btn-inverse-color: var(--color-primary-dark);
--btn-inverse-bg: var(--color-white);
--btn-radius: 20px;
--btn-height: 40px;
/* Header */
--header-bg: var(--color-bg);
--header-color: #666;
--header-bg: var(--color-white);
--header-color: var(--color-text);
--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-hover-bg: rgba(100, 100, 100, 0.15);
/* Header -> search */
--search-bg: rgba(255, 255, 255, 0.7);
--search-border: 1px solid #ccc;
--search-color: #666;
--search-bg-color: #fff;
--search-active-color: var(--header-active-color);
--search-active-shadow: 0 0 3px 0px var(--text-selection-bg);
/* 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);
--search-active-shadow: 0 6px 9px -2px var(--color-grey--dark);
/* Table */
--table-border: 1px solid #e2e2e2;
@ -121,13 +110,10 @@ $text-color: #000;
--table-item-odd: #f4f4f4;
/* Card */
--card-bg: var(--color-bg);
--card-hover-translate: 10px;
--card-margin: $spacing-vertical * 2/3;
--card-max-width: $width-page-constrained;
--card-padding: $spacing-vertical * 2/3;
--card-radius: 2px;
--card-link-scaling: 1.1;
--card-small-width: $spacing-vertical * 10;
/* Modal */
@ -136,12 +122,7 @@ $text-color: #000;
--modal-overlay-bg: rgba(#f5f5f5, 0.75); // --color-canvas: #F5F5F5
--modal-border: 1px solid rgb(204, 204, 204);
/* Menu */
--menu-bg: var(--color-bg);
--menu-radius: 2px;
--menu-item-hover-bg: var(--color-bg-alt);
/* Tooltip */
// /* Tooltip */
--tooltip-width: 300px;
--tooltip-bg: var(--color-bg);
--tooltip-color: var(--text-color);
@ -153,10 +134,10 @@ $text-color: #000;
--scrollbar-thumb-active-bg: var(--color-primary);
--scrollbar-track-bg: transparent;
/* Divider */
--divider: 1px solid rgba(0, 0, 0, 0.12);
/* Animation :) */
--animation-duration: 0.3s;
--animation-style: cubic-bezier(0.55, 0, 0.1, 1);
// /* Divider */
// --divider: 1px solid rgba(0, 0, 0, 0.12);
//
// /* Animation :) */
// --animation-duration: 0.3s;
// --animation-style: cubic-bezier(0.55, 0, 0.1, 1);
}

View file

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

View file

@ -3,34 +3,35 @@ TODO:
Determine [disabled] or .disabled
Add <a> support (probably just get rid of button prefix)
*/
button {
.btn {
border: none;
text-decoration: none;
cursor: pointer;
position: relative;
}
button:disabled.btn--disabled {
cursor: default;
background-color: transparent;
}
button.btn {
font-weight: 600;
padding: 10px;
margin: 0 5px;
height: var(--btn-height);
min-width: var(--btn-height);
border-radius: var(--btn-radius);
color: var(--btn-primary-color);
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);
}
.icon + .btn__label {
padding-left: 5px;
}
}
button.btn.btn--alt {
.btn.btn--alt {
color: var(--btn-alt-color);
background-color: #efefef;
background-color: var(--color-white);
&:hover {
color: #111;
@ -46,30 +47,71 @@ button.btn.btn--alt {
}
}
button.btn.btn--circle {
border-radius: 50%;
transition: all 0.2s;
.btn.btn--inverse {
background-color: transparent;
color: var(--btn-inverse-color);
&:hover:not([disabled]) {
border-radius: var(--btn-radius);
&:hover {
background-color: var(--btn-inverse-bg);
}
}
button.btn.btn--inverse {
box-shadow: none;
background-color: transparent;
color: var(--btn-primary-bg);
}
button.btn--link {
.btn.btn--link {
padding: 0;
margin: 0;
background-color: inherit;
font-size: 0.9em;
color: var(--btn-primary-bg); // this should be a different color
font-size: 1em;
color: var(--btn-inverse-color);
border-radius: 0;
display: inline;
&:hover {
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 {
margin-left: auto;
margin-right: auto;
max-width: var(--card-max-width);
border-radius: var(--card-radius);
overflow: auto;
user-select: text;
display: flex;
position: relative;
}
.card--placeholder {
background-color: black;
.card--section {
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 {
width: var(--card-small-width);
min-height: var(--card-small-width);
overflow-x: hidden;
white-space: normal;
}
@ -48,7 +51,8 @@
margin-top: $spacing-vertical * 1/3;
}
// TODO: regular .card__title for show page
// TODO: regular .card__title
// maybe not needed?
.card__title--small {
font-weight: 600;
font-size: 0.9em;
@ -60,145 +64,24 @@
padding-top: $spacing-vertical * 1/3;
}
// .card__title-primary .meta {
// white-space: nowrap;
// overflow: hidden;
// text-overflow: ellipsis;
// }
//
.card-media__internal-links {
position: absolute;
top: 5px;
right: 5px;
}
//
// .card__actions {
// margin-top: 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__content {
margin-top: var(--card-margin);
margin-bottom: var(--card-margin);
}
//
// .card-series-submit {
// margin-left: auto;
// margin-right: auto;
// max-width: var(--card-max-width);
// padding: $spacing-vertical / 2;
// }
.card__actions {
margin-top: var(--card-margin);
display: flex;
}
/*
.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
There are left/right arrows to scroll the cards and view hidden content
*/
@ -208,6 +91,14 @@
width: 100%;
min-width: var(--card-small-width);
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 {
@ -225,6 +116,10 @@
align-items: center;
}
.card-row__scroll-btns {
display: flex;
}
.card-row__scrollhouse {
padding-top: $spacing-vertical * 2/3;
overflow: hidden;
@ -233,26 +128,10 @@
display: inline-block;
vertical-align: top;
margin-left: $spacing-vertical * 2/3;
overflow: visible;
}
.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 {
margin-top: $spacing-vertical;
}
.form-row-submit--with-footer {
margin-bottom: $spacing-vertical;
}
.form-row-phone {
.form-row {
display: flex;
flex-direction: row;
.form-field__input-text {
margin-left: 5px;
width: calc(0.85 * var(--input-width));
.form-field:not(:first-of-type) {
padding-left: $spacing-vertical;
}
}
.form-row__label-row {
margin-top: $spacing-vertical * 5/6;
margin-bottom: 0px;
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__wrapper {
display: flex;
padding: $spacing-vertical / 3 0;
}
.form-field__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 {
height: auto;
.form-field__prefix {
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;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: var(--header-height);
z-index: 3;
box-sizing: border-box;
color: var(--header-color);
background-color: var(--header-bg);
}
.header__actions-left {
display: flex;
padding: 0 5px;
justify-content: space-between;
padding: 0 $spacing-vertical;
align-items: center;
justify-content: space-between;
height: 100%;
background-color: var(--color-bg);
}
.header__actions-right {
margin-left: auto;
padding-left: $spacing-vertical / 2;
display: flex;
}
.header__wunderbar {
z-index: 1;
flex: 1;
max-width: 325px;
min-width: 175px;
overflow: hidden;
overflow: visible;
white-space: nowrap;
text-overflow: ellipsis;
height: 100%;
display: flex;
align-items: center;
padding: 10px 5px;
cursor: text;
}
position: relative;
.wunderbar__input {
height: 50%;
width: 100%;
color: var(--search-color);
padding: 10px;
background-color: #f3f3f3;
border-radius: 10px;
font-size: 0.9em;
&:focus {
// TODO: focus style
.icon {
position: absolute;
left: 10px;
}
}
.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 {
padding: 5px;
padding: 10px;
background-color: var(--header-bg);
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 {
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 {
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"
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:
version "2.5.1"
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-2.5.1.tgz#f7a6c26a3a5faf5d4c2098155d9775e826fd56ee"