Merge branch 'master' into md-fix

This commit is contained in:
Baltazar Gomez 2018-06-01 11:32:05 -06:00 committed by GitHub
commit bb9e4e218d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 216 additions and 146 deletions

View file

@ -1,16 +1,19 @@
matrix:
include:
- os: osx
env: TARGET=mac
osx_image: xcode9.2
language: node_js
node_js: "9"
env:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
- os: linux
env: TARGET=windows
services: docker
language: node_js
node_js: "9"
- os: linux
env: TARGET=linux
language: node_js
node_js: "9"
cache: false
before_install:
- |
@ -20,6 +23,7 @@ before_install:
else
sudo apt-get -qq update
sudo apt-get install -y libsecret-1-dev
sudo apt-get install --no-install-recommends -y gcc-multilib g++-multilib
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.6.0
export PATH="$HOME/.yarn/bin:$PATH"
fi
@ -27,17 +31,15 @@ before_script:
- git lfs pull
script:
- |
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
if [ "$TARGET" == "windows" ]; then
docker run --rm \
--env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') \
-v ${PWD}:/project \
-v ~/.cache/electron:/root/.cache/electron \
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
electronuserland/builder:wine \
/bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn build --linux --win --publish onTag"
else
yarn build --publish onTag
/bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn build --win --publish onTag";
fi
- if [ "$TARGET" == "mac" ]; then yarn build --publish onTag; fi
- if [ "$TARGET" == "linux" ]; then yarn --link-duplicates --pure-lockfile && yarn build --linux --publish onTag; fi
branches:
except:
- "/^v\\d+\\.\\d+\\.\\d+$/"

View file

@ -14,11 +14,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
* Pre-fill publish URL after clicking "Put something here" link ([#1303](https://github.com/lbryio/lbry-app/pull/1303))
* Add Danger JS to automate code reviews ([#1289](https://github.com/lbryio/lbry-app/pull/1289))
* Autoplay downloaded and free media ([#584](https://github.com/lbryio/lbry-app/pull/1453))
* Add 'Go to page' input on channel pagination ([#1166](https://github.com/lbryio/lbry-app/pull/1166))
* Add "View on web" button on file/channel pages with spee.ch link ([#1222](https://github.com/lbryio/lbry-app/pull/1222))
### Changed
* Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313))
* Made font in price badge larger ([#1420](https://github.com/lbryio/lbry-app/pull/1420))
* Store subscriptions in internal database ([#1424](https://github.com/lbryio/lbry-app/pull/1424))
* Move rewards logic to interal api ([#1509](https://github.com/lbryio/lbry-app/pull/1509))
### Fixed
* Fix content-type not shown correctly in file description ([#863](https://github.com/lbryio/lbry-app/pull/863))
@ -36,6 +39,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
* Fix disabled cards(grayed out) ([#1466](https://github.com/lbryio/lbry-app/issues/1466))
* Fix markdown render ([#1179](https://github.com/lbryio/lbry-app/issues/1179))
* Fix new lines not showing correctly after markdown changes ([#1504](https://github.com/lbryio/lbry-app/issues/1504))
* Fix claim ID being null when reporting a claim that was not previously download ([issue#1512](https://github.com/lbryio/lbry-app/issues/1512)) ([PR#1530](https://github.com/lbryio/lbry-app/pull/1530))
## [0.21.3] - 2018-04-23
@ -58,7 +62,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
* App category for Linux ([#877](https://github.com/lbryio/lbry-app/pull/877))
* Add YouTube Sync reward ([#1147](https://github.com/lbryio/lbry-app/pull/1147))
* Retain previous screen sizing on startup ([#338](https://github.com/lbryio/lbry-app/issues/338))
* Add 'Go to page' input on channel pagination ([#1166](https://github.com/lbryio/lbry-app/pull/1166))
### Changed
@ -373,7 +376,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
* Removed placeholder values from price selection form fields, which was causing confusion that these were real values (#426)
* Fixed showing "other currency" help tip in publish form, which was caused due to not "setting" state for price
* Publish page now properly checks for all required fields are filled
* Fixed pagination styling for pages > 5 (#416)
* Fixed sizing on squat videos (#419)
* Support claims no longer show up on Published page (#384)
* Fixed rendering of small prices (#461)
@ -411,7 +413,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
### Added
* Replaced horizontal scrollbars with scroll arrows
* Featured weekly reward content shows with an orange star
* Added pagination to channel pages
### Fixed

View file

@ -146,6 +146,8 @@ There are a few tools integrated to the project that will ease the process of de
manner and, therefore, not begin working on anything reserved (or updated) within the last 3 days.
If someone has been officially assigned an issue via Github's assignment system, it is also not
available. Contributors are encouraged to ask if they have any questions about issue availability.
* The [changelog](https://github.com/lbryio/lbry-app/blob/master/CHANGELOG.md) should be updated to
include a referene to the fix/change/addition. See previous entries for format.
* Once the pull request is visible in the LBRY repo, a LBRY team member will review it and make sure
it is up to our standards. At this point, the contributor may have to change his or her code based
on our suggestions and comments.

View file

@ -50,7 +50,7 @@ distributable packages.
#### Resetting your Packages
If the app isn't building, or `yarn xxx` commands aren't working you may need to just reset your `node_modules`. To do so you can run: `rm -r node_modules && yarn` or `del node_modules && yarn` on Windows.
If the app isn't building, or `yarn xxx` commands aren't working you may need to just reset your `node_modules`. To do so you can run: `rm -r node_modules && yarn` or `del /s /q node_modules && yarn` on Windows.
## Contributing

View file

@ -48,7 +48,7 @@
"formik": "^0.10.4",
"hast-util-sanitize": "^1.1.2",
"keytar": "^4.2.1",
"lbry-redux": "lbryio/lbry-redux#c41899e78415cae6fcb7bfca0e6ba48bb6bfe6c4",
"lbry-redux": "lbryio/lbry-redux#fb27fb66387043be1b7962cd9439845fa73d7d8e",
"localforage": "^1.7.1",
"mixpanel-browser": "^2.17.1",
"moment": "^2.22.0",

View file

@ -75,6 +75,9 @@ app.on('ready', async () => {
await installExtensions();
}
rendererWindow = createWindow(appState);
rendererWindow.webContents.on('devtools-opened', () => {
rendererWindow.webContents.send('devtools-is-opened');
});
tray = createTray(rendererWindow);
});
@ -176,11 +179,11 @@ ipcMain.on('version-info-requested', () => {
},
};
let result = '';
const req = https.get(Object.assign(opts, url.parse(latestReleaseAPIURL)), res => {
const onSuccess = res => {
res.on('data', data => {
result += data;
});
res.on('end', () => {
const tagName = JSON.parse(result).tag_name;
const [, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/);
@ -199,14 +202,27 @@ ipcMain.on('version-info-requested', () => {
}
}
});
});
};
req.on('error', err => {
console.log('Failed to get current version from GitHub. Error:', err);
if (rendererWindow) {
rendererWindow.webContents.send('version-info-received', null);
}
});
const requestLatestRelease = (apiUrl, alreadyRedirected = false) => {
const req = https.get(Object.assign(opts, url.parse(apiUrl)), res => {
if (res.statusCode === 301 || res.statusCode === 302) {
requestLatestRelease(res.headers.location, true);
} else {
onSuccess(res);
}
});
if (alreadyRedirected) return;
req.on('error', err => {
console.log('Failed to get current version from GitHub. Error:', err);
if (rendererWindow) {
rendererWindow.webContents.send('version-info-received', null);
}
});
};
requestLatestRelease(latestReleaseAPIURL);
});
ipcMain.on('get-auth-token', event => {
@ -242,8 +258,11 @@ const isSecondInstance = app.makeSingleInstance(argv => {
// path, so we just strip it off.
// - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as
// an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well.
// - ? also interpreted as an anchor, remove slash also.
if (process.platform === 'win32') {
URI = URI.replace(/\/$/, '').replace('/#', '#');
URI = URI.replace(/\/$/, '')
.replace('/#', '#')
.replace('/?', '?');
}
rendererWindow.webContents.send('open-uri-requested', URI);

View file

@ -4,7 +4,6 @@ import * as React from 'react';
import classnames from 'classnames';
type Props = {
centered?: boolean,
children: React.Node,
padded?: boolean,
verticallyCentered?: boolean,
@ -22,7 +21,6 @@ export class FormRow extends React.PureComponent<Props> {
return (
<div
className={classnames('form-row', {
'form-row--centered': centered,
'form-row--padded': padded,
'form-row--vertically-centered': verticallyCentered,
'form-row--stretch': stretch,

View file

@ -11,6 +11,7 @@ type FileInfo = {
type Props = {
uri: string,
claimId: string,
openModal: ({ id: string }, { uri: string }) => void,
claimIsMine: boolean,
fileInfo: FileInfo,
@ -19,9 +20,7 @@ type Props = {
class FileActions extends React.PureComponent<Props> {
render() {
const { fileInfo, uri, openModal, claimIsMine, vertical } = this.props;
const claimId = fileInfo ? fileInfo.claim_id : '';
const { fileInfo, uri, openModal, claimIsMine, vertical, claimId } = this.props;
const showDelete = fileInfo && Object.keys(fileInfo).length > 0;
return (

View file

@ -35,20 +35,14 @@ class FilePrice extends React.PureComponent<Props> {
render() {
const { costInfo, showFullPrice } = this.props;
const isEstimate = costInfo ? !costInfo.includesData : false;
if (!costInfo) {
return <span className="credit-amount">PRICE</span>;
}
return (
return costInfo ? (
<CreditAmount
amount={costInfo.cost}
isEstimate={isEstimate}
isEstimate={!costInfo.includesData}
showFree
showFullPrice={showFullPrice}
/>
);
) : null;
}
}

View file

@ -1,26 +1,17 @@
import React from 'react';
import { connect } from 'react-redux';
import InviteNew from './view';
import {
selectUserInvitesRemaining,
selectUserInviteNewIsPending,
selectUserInviteNewErrorMessage,
} from 'redux/selectors/user';
import rewards from 'rewards';
import { makeSelectRewardAmountByType } from 'redux/selectors/rewards';
import { doUserInviteNew } from 'redux/actions/user';
import InviteNew from './view';
const select = state => {
const selectReward = makeSelectRewardAmountByType();
return {
errorMessage: selectUserInviteNewErrorMessage(state),
invitesRemaining: selectUserInvitesRemaining(state),
isPending: selectUserInviteNewIsPending(state),
rewardAmount: selectReward(state, { reward_type: rewards.TYPE_REFERRAL }),
};
};
const select = state => ({
errorMessage: selectUserInviteNewErrorMessage(state),
invitesRemaining: selectUserInvitesRemaining(state),
isPending: selectUserInviteNewIsPending(state),
});
const perform = dispatch => ({
inviteNew: email => dispatch(doUserInviteNew(email)),

View file

@ -29,8 +29,7 @@ class FormInviteNew extends React.PureComponent {
}
render() {
const { errorMessage, isPending, rewardAmount } = this.props;
const label = `${__('Get')} ${rewardAmount} LBC`;
const { errorMessage, isPending } = this.props;
return (
<Form onSubmit={this.handleSubmit}>
@ -49,7 +48,7 @@ class FormInviteNew extends React.PureComponent {
/>
</FormRow>
<div className="card__actions">
<Submit label={label} disabled={isPending} />
<Submit label="Invite" disabled={isPending} />
</div>
</Form>
);

View file

@ -16,7 +16,7 @@ const makeSelect = () => {
const select = (state, props) => ({
errorMessage: selectError(state, props),
isPending: selectIsPending(state, props),
reward: selectReward(state, props),
reward: selectReward(state, props.reward_type),
});
return select;

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { selectCurrentPage, selectCurrentParams } from 'lbry-redux';
import { selectCurrentPage, selectCurrentParams, doNotify } from 'lbry-redux';
import Router from './view';
const select = state => ({
@ -7,4 +7,4 @@ const select = state => ({
currentPage: selectCurrentPage(state),
});
export default connect(select, null)(Router);
export default connect(select, { doNotify })(Router);

View file

@ -18,16 +18,21 @@ import InvitePage from 'page/invite';
import BackupPage from 'page/backup';
import SubscriptionsPage from 'page/subscriptions';
const route = (page, routesMap) => {
const route = (props, page, routesMap) => {
const component = routesMap[page];
return component || DiscoverPage;
if (!component) {
props.doNotify({
message: __('Invalid page requested'),
displayType: ['snackbar'],
});
}
return component || routesMap.discover;
};
const Router = props => {
const { currentPage, params } = props;
return route(currentPage, {
return route(props, currentPage, {
auth: <AuthPage params={params} />,
backup: <BackupPage params={params} />,
channel: <ChannelPage params={params} />,

View file

@ -1,4 +1,5 @@
import { connect } from 'react-redux';
import { doNotify, MODALS } from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation';
import { doUserIdentityVerify } from 'redux/actions/user';
import rewards from 'rewards';
@ -8,15 +9,14 @@ import {
selectIdentityVerifyErrorMessage,
} from 'redux/selectors/user';
import UserVerify from './view';
import { doNotify, MODALS } from 'lbry-redux';
const select = (state, props) => {
const select = state => {
const selectReward = makeSelectRewardByType();
return {
isPending: selectIdentityVerifyIsPending(state),
errorMessage: selectIdentityVerifyErrorMessage(state),
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
reward: selectReward(state, rewards.TYPE_NEW_USER),
};
};

View file

@ -0,0 +1,3 @@
import ViewOnWebButton from './view';
export default ViewOnWebButton;

View file

@ -0,0 +1,21 @@
// @flow
import React from 'react';
import * as icons from 'constants/icons';
import Button from 'component/button';
type Props = {
uri: ?string,
};
export default (props: Props) => {
const { uri } = props;
return uri ? (
<Button
iconRight={icons.GLOBE}
button="alt"
label={__('View on Web')}
href={`http://spee.ch/${uri}`}
/>
) : null;
};

View file

@ -25,3 +25,4 @@ export const CHECK = 'CheckCircle';
export const HEART = 'Heart';
export const UNLOCK = 'Unlock';
export const CHECK_SIMPLE = 'Check';
export const GLOBE = 'Globe';

View file

@ -8,7 +8,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { doConditionalAuthNavigate, doDaemonReady, doAutoUpdate } from 'redux/actions/app';
import { doNotify, doBlackListedOutpointsSubscribe } from 'lbry-redux';
import { doNotify, doBlackListedOutpointsSubscribe, isURIValid } from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation';
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
import { doUserEmailVerify } from 'redux/actions/user';
@ -16,9 +16,11 @@ import 'scss/all.scss';
import store from 'store';
import app from './app';
import analytics from './analytics';
import doLogWarningConsoleMessage from './logWarningConsoleMessage';
import { initContextMenu } from './util/contextMenu';
const { autoUpdater } = remote.require('electron-updater');
const APPPAGEURL = 'lbry://?';
autoUpdater.logger = remote.require('electron-log');
@ -42,8 +44,18 @@ ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
})
);
}
} else {
} else if (uri.startsWith(APPPAGEURL)) {
const navpage = uri.replace(APPPAGEURL, '').toLowerCase();
app.store.dispatch(doNavigate(`/${navpage}`));
} else if (isURIValid(uri)) {
app.store.dispatch(doNavigate('/show', { uri }));
} else {
app.store.dispatch(
doNotify({
message: __('Invalid LBRY URL requested'),
displayType: ['snackbar'],
})
);
}
}
});
@ -62,6 +74,11 @@ ipcRenderer.on('window-is-focused', () => {
dock.setBadge('');
});
ipcRenderer.on('devtools-is-opened', () => {
const logOnDevelopment = false;
doLogWarningConsoleMessage(logOnDevelopment);
});
document.addEventListener('dragover', event => {
event.preventDefault();
});

View file

@ -0,0 +1,34 @@
import isDev from 'electron-is-dev';
export default function doLogWarningConsoleMessage(activeOnDev = false) {
if (isDev && !activeOnDev) return;
const style = {
redTitle:
'color: red; font-size: 50px; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;, font-weight: bold;',
normalText: 'font-size: 24px;',
redText:
'color: red; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; font-size: 24px;',
};
console.clear();
console.log('%cScam alert!', style.redTitle);
console.log(
'%cIf someone told you to copy / paste something here you have a chance of being scammed.',
style.normalText
);
console.log(
'%cPasting anything in here could give attackers access to your LBC credits or wallet.',
style.normalText
);
console.log(
"%cIf you don't understand what you are doing here, please close this window and keep your LBC credits/wallet safe.",
style.redText
);
console.log(
'%cIf you do understand exactly what you are doing, you should come work with us https://lbry.io/join-us',
style.normalText
);
}

View file

@ -8,7 +8,7 @@ const select = state => {
const selectReward = makeSelectRewardByType();
return {
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
reward: selectReward(state, rewards.TYPE_NEW_USER),
};
};

View file

@ -6,7 +6,7 @@ import CreditAmount from 'component/common/credit-amount';
class ModalFirstReward extends React.PureComponent {
render() {
const { closeModal, reward } = this.props;
const { closeModal } = this.props;
return (
<Modal
@ -18,10 +18,7 @@ class ModalFirstReward extends React.PureComponent {
>
<section>
<h3 className="modal__header">{__('Your First Reward')}</h3>
<p>
{__('You just earned your first reward of')}{' '}
<CreditAmount amount={reward.reward_amount} />.
</p>
<p>{__('You just earned your first reward!')}</p>
<p>
{__(
"This reward will show in your Wallet in the top right momentarily (if it hasn't already)."

View file

@ -1,12 +1,15 @@
// @flow
import React from 'react';
import BusyIndicator from 'component/common/busy-indicator';
import Button from 'component/button';
import { FormField, FormRow } from 'component/common/form';
import ReactPaginate from 'react-paginate';
import SubscribeButton from 'component/subscribeButton';
import ViewOnWebButton from 'component/viewOnWebButton';
import Page from 'component/page';
import FileList from 'component/fileList';
import type { Claim } from 'types/claim';
import { MODALS } from 'lbry-redux';
type Props = {
uri: string,
@ -19,6 +22,7 @@ type Props = {
fetchClaims: (string, number) => void,
fetchClaimCount: string => void,
navigate: (string, {}) => void,
openModal: (string, {}) => void,
};
class ChannelPage extends React.PureComponent<Props> {
@ -56,8 +60,8 @@ class ChannelPage extends React.PureComponent<Props> {
}
render() {
const { fetching, claimsInChannel, claim, page, totalPages } = this.props;
const { name, permanent_url: permanentUrl } = claim;
const { fetching, claimsInChannel, claim, page, totalPages, uri, openModal } = this.props;
const { name, permanent_url: permanentUrl, claim_id: claimId } = claim;
let contentList;
if (fetching) {
@ -76,7 +80,14 @@ class ChannelPage extends React.PureComponent<Props> {
<section className="card__channel-info card__channel-info--large">
<h1>{name}</h1>
<div className="card__actions card__actions--no-margin">
<Button
button="alt"
iconRight="Send"
label={__('Enjoy this? Send a tip')}
onClick={() => openModal(MODALS.SEND_TIP, { uri })}
/>
<SubscribeButton uri={permanentUrl} channelName={name} />
<ViewOnWebButton uri={`${name}:${claimId}`} />
</div>
</section>
<section>{contentList}</section>

View file

@ -13,6 +13,7 @@ import DateTime from 'component/dateTime';
import * as icons from 'constants/icons';
import Button from 'component/button';
import SubscribeButton from 'component/subscribeButton';
import ViewOnWebButton from 'component/viewOnWebButton';
import Page from 'component/page';
import player from 'render-media';
import * as settings from 'constants/settings';
@ -80,12 +81,7 @@ class FilePage extends React.Component<Props> {
}
checkSubscription = (props: Props) => {
if (
props.claim.value.publisherSignature &&
props.subscriptions
.map(subscription => subscription.channelName)
.indexOf(props.claim.channel_name) !== -1
) {
if (props.subscriptions.find(sub => sub.channelName === props.claim.channel_name)) {
props.checkSubscription({
channelName: props.claim.channel_name,
uri: buildURI(
@ -114,6 +110,7 @@ class FilePage extends React.Component<Props> {
prepareEdit,
navigate,
autoplay,
costInfo,
} = this.props;
// File info
@ -130,6 +127,11 @@ class FilePage extends React.Component<Props> {
if (channelName && channelClaimId) {
subscriptionUri = buildURI({ channelName, claimId: channelClaimId }, false);
}
const speechSharable =
costInfo &&
costInfo.cost === 0 &&
contentType &&
['video', 'image'].includes(contentType.split('/')[0]);
// We want to use the short form uri for editing
// This is what the user is used to seeing, they don't care about the claim id
@ -193,6 +195,14 @@ class FilePage extends React.Component<Props> {
<SubscribeButton uri={subscriptionUri} channelName={channelName} />
</React.Fragment>
)}
{speechSharable && (
<ViewOnWebButton
uri={buildURI({
claimId: claim.claim_id,
contentName: claim.name,
}).slice(7)}
/>
)}
</div>
</div>
<FormRow alignRight>
@ -208,7 +218,7 @@ class FilePage extends React.Component<Props> {
<div className="card__content">
<FileDownloadLink uri={uri} />
<FileActions uri={uri} />
<FileActions uri={uri} claimId={claim.claim_id} />
</div>
<div className="card__content--extra-padding">

View file

@ -1,7 +1,7 @@
import * as ACTIONS from 'constants/action_types';
import Lbryio from 'lbryio';
import { doNotify, MODALS } from 'lbry-redux';
import { selectUnclaimedRewardsByType } from 'redux/selectors/rewards';
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
import { selectUserIsRewardApproved } from 'redux/selectors/user';
import rewards from 'rewards';
@ -30,8 +30,8 @@ export function doRewardList() {
export function doClaimRewardType(rewardType) {
return (dispatch, getState) => {
const state = getState();
const rewardsByType = selectUnclaimedRewardsByType(state);
const reward = rewardsByType[rewardType];
const unclaimedRewards = selectUnclaimedRewards(state);
const reward = unclaimedRewards.find(ur => ur.reward_type === rewardType);
const userIsRewardApproved = selectUserIsRewardApproved(state);
if (!reward || reward.transaction_id) {
@ -84,14 +84,14 @@ export function doClaimRewardType(rewardType) {
export function doClaimEligiblePurchaseRewards() {
return (dispatch, getState) => {
const state = getState();
const rewardsByType = selectUnclaimedRewardsByType(state);
const unclaimedRewards = selectUnclaimedRewards(state);
const userIsRewardApproved = selectUserIsRewardApproved(state);
if (!userIsRewardApproved || !Lbryio.enabled) {
return;
}
if (rewardsByType[rewards.TYPE_FIRST_STREAM]) {
if (unclaimedRewards.find(ur => ur.reward_type === rewards.TYPE_FIRST_STREAM)) {
dispatch(doClaimRewardType(rewards.TYPE_FIRST_STREAM));
} else {
[rewards.TYPE_MANY_DOWNLOADS, rewards.TYPE_FEATURED_DOWNLOAD].forEach(type => {

View file

@ -4,7 +4,7 @@ const reducers = {};
const defaultState = {
fetching: false,
claimedRewardsById: {}, // id => reward
unclaimedRewardsByType: {},
unclaimedRewards: [],
claimPendingByType: {},
claimErrorsByType: {},
};
@ -17,19 +17,19 @@ reducers[ACTIONS.FETCH_REWARDS_STARTED] = state =>
reducers[ACTIONS.FETCH_REWARDS_COMPLETED] = (state, action) => {
const { userRewards } = action.data;
const unclaimedRewards = {};
const unclaimedRewards = [];
const claimedRewards = {};
userRewards.forEach(reward => {
if (reward.transaction_id) {
claimedRewards[reward.id] = reward;
} else {
unclaimedRewards[reward.reward_type] = reward;
unclaimedRewards.push(reward);
}
});
return Object.assign({}, state, {
claimedRewardsById: claimedRewards,
unclaimedRewardsByType: unclaimedRewards,
unclaimedRewards,
fetching: false,
});
};
@ -62,24 +62,21 @@ reducers[ACTIONS.CLAIM_REWARD_STARTED] = (state, action) => {
reducers[ACTIONS.CLAIM_REWARD_SUCCESS] = (state, action) => {
const { reward } = action.data;
const { unclaimedRewards } = state;
const unclaimedRewardsByType = Object.assign({}, state.unclaimedRewardsByType);
const existingReward = unclaimedRewardsByType[reward.reward_type];
const index = unclaimedRewards.findIndex(ur => ur.reward_type === reward.reward_type);
unclaimedRewards.splice(index, 1);
const newReward = Object.assign({}, reward, {
reward_title: existingReward.reward_title,
reward_description: existingReward.reward_description,
});
const { claimedRewardsById } = state;
claimedRewardsById[reward.id] = reward;
const claimedRewardsById = Object.assign({}, state.claimedRewardsById);
claimedRewardsById[reward.id] = newReward;
const newState = {
...state,
unclaimedRewards: [...unclaimedRewards],
claimedRewardsById: { ...claimedRewardsById },
};
const newState = Object.assign({}, state, {
unclaimedRewardsByType,
claimedRewardsById,
});
return setClaimRewardState(newState, newReward, false, '');
return setClaimRewardState(newState, reward, false, '');
};
reducers[ACTIONS.CLAIM_REWARD_FAILURE] = (state, action) => {

View file

@ -1,5 +1,4 @@
import { createSelector } from 'reselect';
import REWARDS from 'rewards';
const selectState = state => state.rewards || {};
@ -26,16 +25,7 @@ export const selectClaimedRewardsByTransactionId = createSelector(selectClaimedR
}, {})
);
export const selectUnclaimedRewards = createSelector(
selectUnclaimedRewardsByType,
byType =>
Object.values(byType).sort(
(a, b) =>
REWARDS.SORT_ORDER.indexOf(a.reward_type) < REWARDS.SORT_ORDER.indexOf(b.reward_type)
? -1
: 1
) || []
);
export const selectUnclaimedRewards = createSelector(selectState, state => state.unclaimedRewards);
export const selectFetchingRewards = createSelector(selectState, state => !!state.fetching);
@ -65,7 +55,8 @@ const selectClaimRewardError = (state, props) =>
export const makeSelectClaimRewardError = () =>
createSelector(selectClaimRewardError, errorMessage => errorMessage);
const selectRewardByType = (state, props) => selectUnclaimedRewardsByType(state)[props.reward_type];
const selectRewardByType = (state, rewardType) =>
selectUnclaimedRewards(state).find(reward => reward.reward_type === rewardType);
export const makeSelectRewardByType = () => createSelector(selectRewardByType, reward => reward);

View file

@ -1,21 +1,6 @@
import { Lbry, doNotify } from 'lbry-redux';
import Lbryio from 'lbryio';
function rewardMessage(type, amount) {
return {
new_developer: __('You earned %s for registering as a new developer.', amount),
new_user: __('You earned %s LBC new user reward.', amount),
verified_email: __('You earned %s LBC for verifying your email address.', amount),
new_channel: __('You earned %s LBC for creating a publisher identity.', amount),
first_stream: __('You earned %s LBC for streaming your first video.', amount),
many_downloads: __('You earned %s LBC for downloading a bunch of things.', amount),
first_publish: __('You earned %s LBC for making your first publication.', amount),
featured_download: __('You earned %s LBC for watching a featured download.', amount),
referral: __('You earned %s LBC for referring someone.', amount),
youtube_creator: __('You earned %s LBC for syncing your YouTube channel.', amount),
}[type];
}
const rewards = {};
rewards.TYPE_NEW_DEVELOPER = 'new_developer';
@ -28,18 +13,6 @@ rewards.TYPE_FIRST_PUBLISH = 'first_publish';
rewards.TYPE_FEATURED_DOWNLOAD = 'featured_download';
rewards.TYPE_REFERRAL = 'referral';
rewards.YOUTUBE_CREATOR = 'youtube_creator';
rewards.SORT_ORDER = [
rewards.TYPE_NEW_USER,
rewards.TYPE_CONFIRM_EMAIL,
rewards.TYPE_FIRST_STREAM,
rewards.TYPE_FIRST_CHANNEL,
rewards.TYPE_FIRST_PUBLISH,
rewards.TYPE_FEATURED_DOWNLOAD,
rewards.TYPE_MANY_DOWNLOADS,
rewards.TYPE_REFERRAL,
rewards.TYPE_NEW_DEVELOPER,
rewards.YOUTUBE_CREATOR,
];
rewards.claimReward = type => {
function requestReward(resolve, reject, params) {
@ -48,7 +21,8 @@ rewards.claimReward = type => {
return;
}
Lbryio.call('reward', 'new', params, 'post').then(reward => {
const message = rewardMessage(type, reward.reward_amount);
const message =
reward.reward_notification || `You have claimed a ${reward.reward_amount} LBC reward.`;
// Display global notice
const action = doNotify({

View file

@ -62,6 +62,10 @@
width: 35px;
}
input.paginate-channel {
width: 35px;
}
&.form-field--auto-height {
height: auto;
}