Merge pull request #936 from lbryio/redesign-wip

App Navigation and Wallet pages groundwork
This commit is contained in:
Sean Yesmunt 2018-01-24 21:35:42 -08:00 committed by GitHub
commit f934b01d83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
92 changed files with 1503 additions and 6042 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
.gitignore vendored
View file

@ -5,5 +5,4 @@
*.pyc
/static/daemon/lbrynet*
/static/locales
npm-debug.log*

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

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

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

@ -5960,11 +5960,7 @@ moment@^2.20.1:
version "2.20.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd"
<<<<<<< HEAD
move-concurrently@^1.0.1, move-concurrently@~1.0.1:
=======
move-concurrently@^1.0.1:
>>>>>>> Merge master branch in to 'redesign'
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
dependencies:
@ -7490,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"
@ -9100,11 +9100,10 @@ unc-path-regex@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
<<<<<<< HEAD
underscore@>1.4.4:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
=======
union-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
@ -9113,7 +9112,6 @@ union-value@^1.0.0:
get-value "^2.0.6"
is-extendable "^0.1.1"
set-value "^0.4.3"
>>>>>>> Merge master branch in to 'redesign'
uniq@^1.0.1:
version "1.0.1"