decent progress

This commit is contained in:
Jeremy Kauffman 2017-08-20 17:42:00 -04:00
parent 2b8296fcc3
commit 2013da3fac
29 changed files with 3018 additions and 690 deletions

View file

@ -8,19 +8,24 @@ Web UI version numbers should always match the corresponding version of LBRY App
## [Unreleased]
### Added
* Added an Invites area inside of the Wallet. This allows users to invite others and shows the status of all past invites (including all invite data from the past year).
* Added a forward button and improved history behavior. Back/forward disable when unusable.
* Added a new component, `FormFieldPrice` which is now used in Publish and Settings.
* Added new summary components for rewards and invites to the Wallet landing page.
* Added past history of rewards to the rewards page.
* Added wallet backup guide reference.
* Added a new widget for setting prices (`FormFieldPrice`), used in Publish and Settings.
### Changed
* Updated to daemon [0.15](https://github.com/lbryio/lbry/releases). Most relevant changes for app are improved announcing of content and a fix for the daemon getting stuck running.
* Continued to refine first-run process, process for new users, and introducing people to LBRY and LBRY credits.
* Changed the default price settings.
* Changed Wallet landing page to summarize status of other areas. Refactored wallet and transaction logic.
* Added icons to missing page, improved icon and title logic.
* Changed the default price settings for priced publishes.
* When an "Open" button is clicked on a show page, if the file fails to open, the app will try to open the file's folder.
* Updated several packages and fixed warnings in build process (all but the [fsevents warning](https://github.com/yarnpkg/yarn/issues/3738), which is a rather dramatic debate)
* Some form field refactoring as we take baby steps towards form sanity.
* Replaced confusing placeholder text from email input.
* Refactored modal and settings logic.
* Updated several packages and fixed warnings in build process (all but the [fsevents warning](https://github.com/yarnpkg/yarn/issues/3738), which is a rather dramatic debate)
### Fixed
* Tiles will no longer be blurry on hover (Windows only bug)

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 306 KiB

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -20,6 +20,7 @@ export function doAuthenticate() {
data: { user },
});
dispatch(doRewardList());
dispatch(doFetchInviteStatus());
})
.catch(error => {
dispatch(doOpenModal(modals.AUTHENTICATION_FAILURE));

View file

@ -69,15 +69,18 @@ export class CreditAmount extends React.PureComponent {
label: React.PropTypes.bool,
showFree: React.PropTypes.bool,
showFullPrice: React.PropTypes.bool,
showPlus: React.PropTypes.bool,
look: React.PropTypes.oneOf(["indicator", "plain"]),
};
static defaultProps = {
precision: 2,
label: true,
showFree: false,
look: "indicator",
showFree: false,
showFullPrice: false,
showPlus: false,
};
render() {
@ -98,13 +101,18 @@ export class CreditAmount extends React.PureComponent {
let amountText;
if (this.props.showFree && parseFloat(this.props.amount) === 0) {
amountText = __("free");
} else if (this.props.label) {
amountText =
formattedAmount +
" " +
(parseFloat(amount) == 1 ? __("credit") : __("credits"));
} else {
amountText = formattedAmount;
if (this.props.label) {
amountText =
formattedAmount +
" " +
(parseFloat(amount) == 1 ? __("credit") : __("credits"));
} else {
amountText = formattedAmount;
}
if (this.props.showPlus && amount > 0) {
amountText = "+" + amountText;
}
}
return (

View file

@ -0,0 +1,14 @@
import React from "react";
import { connect } from "react-redux";
import {
selectUserInvitesRemaining,
selectUserInviteNewIsPending,
} from "selectors/user";
import InviteSummary from "./view";
const select = state => ({
invitesRemaining: selectUserInvitesRemaining(state),
isPending: selectUserInviteNewIsPending(state),
});
export default connect(select)(InviteSummary);

View file

@ -0,0 +1,36 @@
import React from "react";
import Link from "component/link";
import { CreditAmount, BusyMessage } from "component/common";
const InviteSummary = props => {
const { isPending, invitesRemaining } = props;
console.log(invitesRemaining);
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Invites")}</h3>
</div>
<div className="card__content">
{isPending && <BusyMessage message={__("Checking invite status")} />}
{!isPending &&
<p>
{__n(
"You have %d invite remaining.",
"You have %d invites remaining.",
invitesRemaining
)}
</p>}
</div>
<div className="card__content">
<Link
button={invitesRemaining > 0 ? "primary" : "text"}
navigate="/invite"
label={__("Go To Invites")}
/>
</div>
</section>
);
};
export default InviteSummary;

View file

@ -1,5 +1,10 @@
import React from "react";
import { connect } from "react-redux";
import { doNavigate } from "actions/app";
import Link from "./view";
export default connect(null, null)(Link);
const perform = dispatch => ({
doNavigate: path => dispatch(doNavigate(path)),
});
export default connect(null, perform)(Link);

View file

@ -5,7 +5,6 @@ const Link = props => {
const {
href,
title,
onClick,
style,
label,
icon,
@ -13,6 +12,8 @@ const Link = props => {
button,
disabled,
children,
navigate,
doNavigate,
} = props;
const className =
@ -21,6 +22,12 @@ const Link = props => {
(button ? " button-block button-" + button + " button-set-item" : "") +
(disabled ? " disabled" : "");
const onClick = props.onClick
? props.onClick
: () => {
doNavigate(navigate);
};
let content;
if (children) {
content = children;

View file

@ -318,7 +318,7 @@ class PublishForm extends React.PureComponent {
handleFeePrefChange(feeEnabled) {
this.setState({
isFee: feeEnabled,
feeAmount: this.state.feeAmount == "" ? "5.00" : this.state.feeAmount,
feeAmount: this.state.feeAmount == "" ? "0.01" : this.state.feeAmount,
});
}
@ -786,7 +786,6 @@ class PublishForm extends React.PureComponent {
ref="bid"
type="number"
step="0.01"
min="0"
label={__("Deposit")}
postfix="LBC"
onChange={event => {

View file

@ -0,0 +1,11 @@
import React from "react";
import { connect } from "react-redux";
import { doNavigate } from "actions/app";
import { selectUnclaimedRewardValue } from "selectors/rewards";
import RewardSummary from "./view";
const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
});
export default connect(select, null)(RewardSummary);

View file

@ -0,0 +1,32 @@
import React from "react";
import Link from "component/link";
import { CreditAmount } from "component/common";
const RewardSummary = props => {
const { balance, unclaimedRewardAmount } = props;
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Rewards")}</h3>
</div>
<div className="card__content">
{unclaimedRewardAmount > 0 &&
<p>
You have{" "}
<CreditAmount amount={unclaimedRewardAmount} precision={8} /> in
unclaimed rewards.
</p>}
</div>
<div className="card__content">
<Link
button={unclaimedRewardAmount > 0 ? "primary" : "text"}
navigate="/rewards"
label={__("Go To Rewards")}
/>
</div>
</section>
);
};
export default RewardSummary;

View file

@ -1,21 +1,5 @@
import React from "react";
import { connect } from "react-redux";
import { doFetchTransactions } from "actions/wallet";
import {
selectBalance,
selectTransactionItems,
selectIsFetchingTransactions,
} from "selectors/wallet";
import TransactionList from "./view";
const select = state => ({
fetchingTransactions: selectIsFetchingTransactions(state),
transactionItems: selectTransactionItems(state),
});
const perform = dispatch => ({
fetchTransactions: () => dispatch(doFetchTransactions()),
});
export default connect(select, perform)(TransactionList);
export default connect(null, null)(TransactionList);

View file

@ -1,69 +1,57 @@
import React from "react";
import { BusyMessage } from "component/common";
import LinkTransaction from "component/linkTransaction";
import { CreditAmount } from "component/common";
class TransactionList extends React.PureComponent {
componentWillMount() {
this.props.fetchTransactions();
}
render() {
const { fetchingTransactions, transactionItems } = this.props;
const rows = [];
if (transactionItems.length > 0) {
transactionItems.forEach(function(item) {
rows.push(
<tr key={item.id}>
<td>{(item.amount > 0 ? "+" : "") + item.amount}</td>
<td>
{item.date
? item.date.toLocaleDateString()
: <span className="empty">{__("(Transaction pending)")}</span>}
</td>
<td>
{item.date
? item.date.toLocaleTimeString()
: <span className="empty">{__("(Transaction pending)")}</span>}
</td>
<td>
<LinkTransaction id={item.id} />
</td>
</tr>
);
});
}
const TransactionList = props => {
const { emptyMessage, transactions } = props;
if (!transactions || !transactions.length) {
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Transaction History")}</h3>
</div>
<div className="card__content">
{fetchingTransactions &&
<BusyMessage message={__("Loading transactions")} />}
{!fetchingTransactions && rows.length === 0
? <div className="empty">{__("You have no transactions.")}</div>
: ""}
{rows.length > 0
? <table className="table-standard table-stretch">
<thead>
<tr>
<th>{__("Amount")}</th>
<th>{__("Date")}</th>
<th>{__("Time")}</th>
<th>{__("Transaction")}</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
: ""}
</div>
</section>
<div className="empty">
{emptyMessage || __("No transactions to list.")}
</div>
);
}
}
return (
<table className="table-standard table-stretch">
<thead>
<tr>
<th>{__("Date")}</th>
<th>{__("Amount")}</th>
<th>{__("Transaction")}</th>
</tr>
</thead>
<tbody>
{transactions.map(item => {
return (
<tr key={item.id}>
<td>
{item.date
? item.date.toLocaleDateString() +
" " +
item.date.toLocaleTimeString()
: <span className="empty">
{__("(Transaction pending)")}
</span>}
</td>
<td>
<CreditAmount
amount={item.amount}
look="plain"
showPlus={true}
precision={8}
/>{" "}
</td>
<td>
<LinkTransaction id={item.id} />
</td>
</tr>
);
})}
</tbody>
</table>
);
};
export default TransactionList;

View file

@ -0,0 +1,23 @@
import React from "react";
import { connect } from "react-redux";
import { doFetchTransactions } from "actions/wallet";
import {
selectBalance,
selectRecentTransactions,
selectHasTransactions,
selectIsFetchingTransactions,
} from "selectors/wallet";
import TransactionListRecent from "./view";
const select = state => ({
fetchingTransactions: selectIsFetchingTransactions(state),
transactions: selectRecentTransactions(state),
hasTransactions: selectHasTransactions(state),
});
const perform = dispatch => ({
fetchTransactions: () => dispatch(doFetchTransactions()),
});
export default connect(select, perform)(TransactionListRecent);

View file

@ -0,0 +1,41 @@
import React from "react";
import { BusyMessage } from "component/common";
import Link from "component/link";
import TransactionList from "component/transactionList";
class TransactionListRecent extends React.PureComponent {
componentWillMount() {
this.props.fetchTransactions();
}
render() {
const { fetchingTransactions, hasTransactions, transactions } = this.props;
console.log(transactions);
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Recent Transactions")}</h3>
</div>
<div className="card__content">
{fetchingTransactions &&
<BusyMessage message={__("Loading transactions")} />}
{!fetchingTransactions &&
<TransactionList
transactions={transactions}
emptyMessage={__("You have no recent transactions.")}
/>}
</div>
{hasTransactions &&
<div className="card__content">
<Link
navigate="/history"
label={__("See Full History")}
button="text"
/>
</div>}
</section>
);
}
}
export default TransactionListRecent;

View file

@ -1,6 +1,5 @@
import React from "react";
import { connect } from "react-redux";
import { doNavigate } from "actions/app";
import { selectBalance } from "selectors/wallet";
import WalletBalance from "./view";
@ -8,8 +7,4 @@ const select = state => ({
balance: selectBalance(state),
});
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
});
export default connect(select, perform)(WalletBalance);
export default connect(select, null)(WalletBalance);

View file

@ -4,7 +4,14 @@ import { CreditAmount } from "component/common";
const WalletBalance = props => {
const { balance, navigate } = props;
/*
<div className="help">
<Link
onClick={() => navigate("/backup")}
label={__("Backup Your Wallet")}
/>
</div>
*/
return (
<section className="card">
<div className="card__title-primary">
@ -14,12 +21,13 @@ const WalletBalance = props => {
{balance && <CreditAmount amount={balance} precision={8} />}
</div>
<div className="card__content">
<div className="help">
<Link
onClick={() => navigate("/backup")}
label={__("Backup Your Wallet")}
/>
</div>
<Link button="text" navigate="/send" label={__("Send")} />
<Link button="text" navigate="/receive" label={__("Receive")} />
<Link
button="text"
navigate="/backup"
label={__("Backup Your Wallet")}
/>
</div>
</section>
);

View file

@ -1,5 +1,19 @@
import React from "react";
import { connect } from "react-redux";
import WalletPage from "./view";
import { doFetchTransactions } from "actions/wallet";
import {
selectTransactionItems,
selectIsFetchingTransactions,
} from "selectors/wallet";
import TransactionHistoryPage from "./view";
export default connect(null, null)(WalletPage);
const select = state => ({
fetchingTransactions: selectIsFetchingTransactions(state),
transactions: selectTransactionItems(state),
});
const perform = dispatch => ({
fetchTransactions: () => dispatch(doFetchTransactions()),
});
export default connect(select, perform)(TransactionHistoryPage);

View file

@ -1,14 +1,32 @@
import React from "react";
import { BusyMessage } from "component/common";
import SubHeader from "component/subHeader";
import TransactionList from "component/transactionList";
const TransactionHistoryPage = props => {
return (
<main className="main--single-column">
<SubHeader />
<TransactionList />
</main>
);
};
class TransactionHistoryPage extends React.PureComponent {
componentWillMount() {
this.props.fetchTransactions();
}
render() {
const { fetchingTransactions, transactions } = this.props;
return (
<main className="main--single-column">
<SubHeader />
<section className="card">
<div className="card__title-primary">
<h3>{__("Transaction History")}</h3>
</div>
<div className="card__content">
{fetchingTransactions &&
<BusyMessage message={__("Loading transactions")} />}
{!fetchingTransactions &&
<TransactionList transactions={transactions} />}
</div>
</section>
</main>
);
}
}
export default TransactionHistoryPage;

View file

@ -1,14 +1,18 @@
import React from "react";
import SubHeader from "component/subHeader";
import WalletBalance from "component/walletBalance";
import TransactionList from "component/transactionList";
import RewardSummary from "component/rewardSummary";
import InviteSummary from "component/inviteSummary";
import TransactionListRecent from "component/transactionListRecent";
const WalletPage = props => {
return (
<main className="main--single-column">
<SubHeader />
<WalletBalance />
<TransactionList />
<RewardSummary />
<InviteSummary />
<TransactionListRecent />
</main>
);
};

View file

@ -39,13 +39,15 @@ export const selectPageTitle = createSelector(
case "wallet":
return __("Wallet");
case "send":
return __("Send");
return __("Send Credits");
case "receive":
return __("Receive");
return __("Receive Credits");
case "backup":
return __("Backup");
return __("Backup Your Wallet");
case "rewards":
return __("Rewards");
case "invite":
return __("Invites");
case "start":
return __("Start");
case "publish":
@ -72,8 +74,12 @@ export const selectPageTitle = createSelector(
return __("Publishes");
case "discover":
return __("Home");
default:
case false:
case null:
case "":
return "";
default:
return page[0].toUpperCase() + (page.length > 0 ? page.substr(1) : "");
}
}
);
@ -144,11 +150,11 @@ export const selectHeaderLinks = createSelector(selectCurrentPage, page => {
case "backup":
return {
wallet: __("Overview"),
rewards: __("Rewards"),
invite: __("Invites"),
history: __("History"),
send: __("Send"),
receive: __("Receive"),
invite: __("Invites"),
rewards: __("Rewards"),
};
case "downloaded":
case "published":

View file

@ -52,22 +52,27 @@ export const selectWunderBarIcon = createSelector(selectCurrentPage, page => {
return "icon-folder";
case "start":
return "icon-file";
case "rewards":
return "icon-bank";
case "wallet":
case "history":
return "icon-history";
case "send":
return "icon-send";
case "rewards":
return "icon-rocket";
case "invite":
return "icon-envelope-open";
case "receive":
case "wallet":
case "backup":
return "icon-bank";
case "show":
return "icon-file";
case "publish":
return "icon-upload";
case "developer":
return "icon-file";
case "developer":
return "icon-code";
case "discover":
return "icon-home";
default:
return "icon-file";
}
});

View file

@ -35,6 +35,24 @@ export const selectTransactionItems = createSelector(
}
);
export const selectRecentTransactions = createSelector(
selectTransactionItems,
transactions => {
let threshold = new Date();
threshold.setDate(threshold.getDate() - 7);
return transactions.filter(transaction => {
return transaction.date > threshold;
});
}
);
export const selectHasTransactions = createSelector(
selectTransactionItems,
transactions => {
return transactions && transactions.length > 0;
}
);
export const selectIsFetchingTransactions = createSelector(
_selectState,
state => state.fetchingTransactions

View file

@ -2,8 +2,8 @@
@font-face {
font-family: 'FontAwesome';
src: url('../font/fontawesome-webfont.eot?v=4.3.0');
src: url('../font/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'), url('../font/fontawesome-webfont.woff2?v=4.3.0') format('woff2'), url('../font/fontawesome-webfont.woff?v=4.3.0') format('woff'), url('../font/fontawesome-webfont.ttf?v=4.3.0') format('truetype'), url('../font/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');
src: url('../font/fontawesome-webfont.eot?v=4.7.0');
src: url('../font/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../font/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../font/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../font/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../font/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}