initial onboarding commit
This commit is contained in:
parent
5c06fa2dd8
commit
3bee71f514
94 changed files with 1873 additions and 1627 deletions
|
@ -29,6 +29,7 @@
|
||||||
"jsx-quotes": ["error", "prefer-double"],
|
"jsx-quotes": ["error", "prefer-double"],
|
||||||
"new-cap": 0,
|
"new-cap": 0,
|
||||||
"no-console": 1,
|
"no-console": 1,
|
||||||
|
"no-control-regex": 0,
|
||||||
"no-multi-spaces": 0,
|
"no-multi-spaces": 0,
|
||||||
"no-redeclare": 0,
|
"no-redeclare": 0,
|
||||||
"no-return-await": 0,
|
"no-return-await": 0,
|
||||||
|
@ -38,11 +39,14 @@
|
||||||
"react/jsx-indent": 0,
|
"react/jsx-indent": 0,
|
||||||
"react-hooks/exhaustive-deps": "warn",
|
"react-hooks/exhaustive-deps": "warn",
|
||||||
"react-hooks/rules-of-hooks": "error",
|
"react-hooks/rules-of-hooks": "error",
|
||||||
"space-before-function-paren": ["error", {
|
"space-before-function-paren": [
|
||||||
"anonymous": "never",
|
"error",
|
||||||
"named": "never",
|
{
|
||||||
"asyncArrow": "always"
|
"anonymous": "never",
|
||||||
}],
|
"named": "never",
|
||||||
|
"asyncArrow": "always"
|
||||||
|
}
|
||||||
|
],
|
||||||
"standard/object-curly-even-spacing": 0,
|
"standard/object-curly-even-spacing": 0,
|
||||||
"standard/no-callback-literal": 0,
|
"standard/no-callback-literal": 0,
|
||||||
"react/display-name": 0,
|
"react/display-name": 0,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
[libs]
|
[libs]
|
||||||
./flow-typed
|
./flow-typed
|
||||||
node_modules/lbry-redux/flow-typed/
|
node_modules/lbry-redux/flow-typed/
|
||||||
|
node_modules/lbryinc/flow-typed/
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|
||||||
|
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
FROM node:10
|
||||||
|
EXPOSE 1337
|
||||||
|
|
||||||
|
RUN yarn -v && npm -v
|
||||||
|
RUN apt-get update -y && apt-get upgrade -y && apt-get install -y libsecret-1-0 libsecret-1-dev
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
ENV PATH="/app/node_modules/.bin:${PATH}"
|
||||||
|
|
||||||
|
COPY ./ ./
|
||||||
|
|
||||||
|
RUN rm -rf node_modules && APP_ENV=web yarn && SDK_API_URL='https://api.lbry.tv/api/proxy' NODE_ENV=production yarn compile:web --display errors-only
|
||||||
|
CMD node ./dist/web/server.js
|
60
flow-typed/npm/react-pose_vx.x.x.js
vendored
60
flow-typed/npm/react-pose_vx.x.x.js
vendored
|
@ -1,60 +0,0 @@
|
||||||
// flow-typed signature: dbdb6148e2902ceaf3e437a7fe96ffa1
|
|
||||||
// flow-typed version: <<STUB>>/react-pose_v^4.0.5/flow_v0.94.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an autogenerated libdef stub for:
|
|
||||||
*
|
|
||||||
* 'react-pose'
|
|
||||||
*
|
|
||||||
* Fill this stub out by replacing all the `any` types.
|
|
||||||
*
|
|
||||||
* Once filled out, we encourage you to share your work with the
|
|
||||||
* community by sending a pull request to:
|
|
||||||
* https://github.com/flowtype/flow-typed
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare module 'react-pose' {
|
|
||||||
declare module.exports: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We include stubs for each file inside this npm package in case you need to
|
|
||||||
* require those files directly. Feel free to delete any files that aren't
|
|
||||||
* needed.
|
|
||||||
*/
|
|
||||||
declare module 'react-pose/dist/react-pose.dev' {
|
|
||||||
declare module.exports: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'react-pose/dist/react-pose.es' {
|
|
||||||
declare module.exports: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'react-pose/dist/react-pose' {
|
|
||||||
declare module.exports: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'react-pose/lib/index' {
|
|
||||||
declare module.exports: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'react-pose/rollup.config' {
|
|
||||||
declare module.exports: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filename aliases
|
|
||||||
declare module 'react-pose/dist/react-pose.dev.js' {
|
|
||||||
declare module.exports: $Exports<'react-pose/dist/react-pose.dev'>;
|
|
||||||
}
|
|
||||||
declare module 'react-pose/dist/react-pose.es.js' {
|
|
||||||
declare module.exports: $Exports<'react-pose/dist/react-pose.es'>;
|
|
||||||
}
|
|
||||||
declare module 'react-pose/dist/react-pose.js' {
|
|
||||||
declare module.exports: $Exports<'react-pose/dist/react-pose'>;
|
|
||||||
}
|
|
||||||
declare module 'react-pose/lib/index.js' {
|
|
||||||
declare module.exports: $Exports<'react-pose/lib/index'>;
|
|
||||||
}
|
|
||||||
declare module 'react-pose/rollup.config.js' {
|
|
||||||
declare module.exports: $Exports<'react-pose/rollup.config'>;
|
|
||||||
}
|
|
24
flow-typed/user.js
vendored
24
flow-typed/user.js
vendored
|
@ -1,24 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
// Move this to lbryinc
|
|
||||||
declare type User = {
|
|
||||||
created_at: string,
|
|
||||||
family_name: ?string,
|
|
||||||
given_name: ?string,
|
|
||||||
groups: Array<string>,
|
|
||||||
has_verified_email: boolean,
|
|
||||||
id: number,
|
|
||||||
invite_reward_claimed: boolean,
|
|
||||||
invited_at: ?number,
|
|
||||||
invited_by_id: number,
|
|
||||||
invites_remaining: number,
|
|
||||||
is_email_enabled: boolean,
|
|
||||||
is_identity_verified: boolean,
|
|
||||||
is_reward_approved: boolean,
|
|
||||||
language: string,
|
|
||||||
manual_approval_user_id: ?number,
|
|
||||||
primary_email: string,
|
|
||||||
reward_status_change_trigger: string,
|
|
||||||
updated_at: string,
|
|
||||||
youtube_channels: ?Array<string>,
|
|
||||||
};
|
|
|
@ -128,8 +128,8 @@
|
||||||
"husky": "^0.14.3",
|
"husky": "^0.14.3",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#64383d57873ce59dea9df7216ee6cf52c4e95dc6",
|
"lbry-redux": "lbryio/lbry-redux#42bf926138872d14523be7191694309be4f37605",
|
||||||
"lbryinc": "lbryio/lbryinc#d250096a6fc5df16be4f82812ecce28d6e558b6e",
|
"lbryinc": "lbryio/lbryinc#67bb3e215be3f13605c5e3f9f2b0e2fb880724cf",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
"lodash-es": "^4.17.14",
|
"lodash-es": "^4.17.14",
|
||||||
|
@ -157,7 +157,6 @@
|
||||||
"react-hot-loader": "^4.11.1",
|
"react-hot-loader": "^4.11.1",
|
||||||
"react-modal": "^3.1.7",
|
"react-modal": "^3.1.7",
|
||||||
"react-paginate": "^5.2.1",
|
"react-paginate": "^5.2.1",
|
||||||
"react-pose": "^4.0.5",
|
|
||||||
"react-redux": "^6.0.1",
|
"react-redux": "^6.0.1",
|
||||||
"react-router": "^5.0.0",
|
"react-router": "^5.0.0",
|
||||||
"react-router-dom": "^5.0.0",
|
"react-router-dom": "^5.0.0",
|
||||||
|
|
|
@ -17,7 +17,7 @@ export default appState => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const windowConfiguration = {
|
const windowConfiguration = {
|
||||||
backgroundColor: '#270f34', // Located in src/scss/init/_vars.scss `--color-background`
|
backgroundColor: '#270f34', // Located in src/scss/init/_vars.scss `--color-background--splash`
|
||||||
minWidth: 950,
|
minWidth: 950,
|
||||||
minHeight: 600,
|
minHeight: 600,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
|
|
|
@ -31,8 +31,7 @@ let showingAutoUpdateCloseAlert = false;
|
||||||
// object is garbage collected.
|
// object is garbage collected.
|
||||||
let rendererWindow;
|
let rendererWindow;
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
let tray; // eslint-disable-line
|
||||||
let tray;
|
|
||||||
let daemon;
|
let daemon;
|
||||||
|
|
||||||
const appState = {};
|
const appState = {};
|
||||||
|
@ -47,7 +46,6 @@ if (isDev) {
|
||||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
|
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line space-before-function-paren
|
|
||||||
const startDaemon = async () => {
|
const startDaemon = async () => {
|
||||||
let isDaemonRunning = false;
|
let isDaemonRunning = false;
|
||||||
|
|
||||||
|
@ -114,7 +112,6 @@ if (!gotSingleInstanceLock) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line space-before-function-paren
|
|
||||||
app.on('ready', async () => {
|
app.on('ready', async () => {
|
||||||
await startDaemon();
|
await startDaemon();
|
||||||
startSandbox();
|
startSandbox();
|
||||||
|
@ -317,6 +314,12 @@ ipcMain.on('set-auth-token', (event, token) => {
|
||||||
keytar.setPassword('LBRY', 'auth_token', token ? token.toString().trim() : null);
|
keytar.setPassword('LBRY', 'auth_token', token ? token.toString().trim() : null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.on('delete-auth-token', (event, password) => {
|
||||||
|
keytar.deletePassword('LBRY', 'auth_token', password).then(res => {
|
||||||
|
event.sender.send('delete-auth-token-response', res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.on('get-password', event => {
|
ipcMain.on('get-password', event => {
|
||||||
keytar.getPassword('LBRY', 'wallet_password').then(password => {
|
keytar.getPassword('LBRY', 'wallet_password').then(password => {
|
||||||
event.sender.send('get-password-response', password ? password.toString() : null);
|
event.sender.send('get-password-response', password ? password.toString() : null);
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
const { parseURI } = require('lbry-redux');
|
const { parseURI } = require('lbry-redux');
|
||||||
// const { generateStreamUrl } = require('../../src/ui/util/lbrytv');
|
// const { generateStreamUrl } = require('../../src/ui/util/lbrytv');
|
||||||
function generateStreamUrl(claimName, claimId) {
|
|
||||||
return `https://api.lbry.tv/content/claims/${claimName}/${claimId}/stream`;
|
|
||||||
}
|
|
||||||
const { WEB_SERVER_PORT } = require('../../config');
|
const { WEB_SERVER_PORT } = require('../../config');
|
||||||
const { readFileSync } = require('fs');
|
const { readFileSync } = require('fs');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
@ -72,7 +69,7 @@ app.get('*', async (req, res) => {
|
||||||
let html = readFileSync(path.join(__dirname, '/index.html'), 'utf8');
|
let html = readFileSync(path.join(__dirname, '/index.html'), 'utf8');
|
||||||
const urlPath = req.path.substr(1); // trim leading slash
|
const urlPath = req.path.substr(1); // trim leading slash
|
||||||
|
|
||||||
if (urlPath.match(/^([^@/:]+)\/([^:/]+)$/)) {
|
if (!urlPath.startsWith('$/') && urlPath.match(/^([^@/:]+)\/([^:/]+)$/)) {
|
||||||
return res.redirect(301, req.url.replace(/([^/:]+)\/([^:/]+)/, '$1:$2')); // test against urlPath, but use req.url to retain parameters
|
return res.redirect(301, req.url.replace(/([^/:]+)\/([^:/]+)/, '$1:$2')); // test against urlPath, but use req.url to retain parameters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doFetchTransactions } from 'lbry-redux';
|
|
||||||
import { selectUser, doRewardList, doFetchRewardedContent, doFetchAccessToken, selectAccessToken } from 'lbryinc';
|
import { selectUser, doRewardList, doFetchRewardedContent, doFetchAccessToken, selectAccessToken } from 'lbryinc';
|
||||||
|
import { doFetchTransactions, doFetchChannelListMine } from 'lbry-redux';
|
||||||
import { makeSelectClientSetting, selectThemePath } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectThemePath } from 'redux/selectors/settings';
|
||||||
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
|
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
|
||||||
import { doDownloadUpgradeRequested } from 'redux/actions/app';
|
import { doDownloadUpgradeRequested, doSignIn } from 'redux/actions/app';
|
||||||
import * as SETTINGS from 'constants/settings';
|
|
||||||
import App from './view';
|
import App from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -23,6 +23,8 @@ const perform = dispatch => ({
|
||||||
fetchTransactions: () => dispatch(doFetchTransactions()),
|
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||||
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
|
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
|
||||||
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
||||||
|
onSignedIn: () => dispatch(doSignIn()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default hot(
|
export default hot(
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import { Lbry, buildURI, parseURI } from 'lbry-redux';
|
import { buildURI, parseURI } from 'lbry-redux';
|
||||||
import Router from 'component/router/index';
|
import Router from 'component/router/index';
|
||||||
import ModalRouter from 'modal/modalRouter';
|
import ModalRouter from 'modal/modalRouter';
|
||||||
import ReactModal from 'react-modal';
|
import ReactModal from 'react-modal';
|
||||||
|
@ -12,6 +12,7 @@ import Yrbl from 'component/yrbl';
|
||||||
import FileViewer from 'component/fileViewer';
|
import FileViewer from 'component/fileViewer';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import usePrevious from 'util/use-previous';
|
import usePrevious from 'util/use-previous';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
|
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
|
||||||
|
|
||||||
|
@ -20,7 +21,6 @@ type Props = {
|
||||||
pageTitle: ?string,
|
pageTitle: ?string,
|
||||||
language: string,
|
language: string,
|
||||||
theme: string,
|
theme: string,
|
||||||
accessToken: ?string,
|
|
||||||
user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean },
|
user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean },
|
||||||
location: { pathname: string, hash: string },
|
location: { pathname: string, hash: string },
|
||||||
fetchRewards: () => void,
|
fetchRewards: () => void,
|
||||||
|
@ -30,6 +30,8 @@ type Props = {
|
||||||
autoUpdateDownloaded: boolean,
|
autoUpdateDownloaded: boolean,
|
||||||
isUpgradeAvailable: boolean,
|
isUpgradeAvailable: boolean,
|
||||||
requestDownloadUpgrade: () => void,
|
requestDownloadUpgrade: () => void,
|
||||||
|
fetchChannelListMine: () => void,
|
||||||
|
onSignedIn: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function App(props: Props) {
|
function App(props: Props) {
|
||||||
|
@ -40,10 +42,11 @@ function App(props: Props) {
|
||||||
fetchTransactions,
|
fetchTransactions,
|
||||||
user,
|
user,
|
||||||
fetchAccessToken,
|
fetchAccessToken,
|
||||||
accessToken,
|
|
||||||
requestDownloadUpgrade,
|
requestDownloadUpgrade,
|
||||||
autoUpdateDownloaded,
|
autoUpdateDownloaded,
|
||||||
isUpgradeAvailable,
|
isUpgradeAvailable,
|
||||||
|
fetchChannelListMine,
|
||||||
|
onSignedIn,
|
||||||
} = props;
|
} = props;
|
||||||
const appRef = useRef();
|
const appRef = useRef();
|
||||||
const isEnhancedLayout = useKonamiListener();
|
const isEnhancedLayout = useKonamiListener();
|
||||||
|
@ -70,8 +73,9 @@ function App(props: Props) {
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
fetchRewards();
|
fetchRewards();
|
||||||
fetchTransactions();
|
fetchTransactions();
|
||||||
|
fetchChannelListMine(); // This needs to be done for web too...
|
||||||
// @endif
|
// @endif
|
||||||
}, [fetchRewards, fetchRewardedContent, fetchTransactions, fetchAccessToken]);
|
}, [fetchRewards, fetchRewardedContent, fetchTransactions, fetchAccessToken, fetchChannelListMine]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
|
@ -87,24 +91,27 @@ function App(props: Props) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check that previousHasVerifiedEmail was not undefined instead of just not truthy
|
// Check that previousHasVerifiedEmail was not undefined instead of just not truthy
|
||||||
// This ensures we don't fire the emailVerified event on the initial user fetch
|
// This ensures we don't fire the emailVerified event on the initial user fetch
|
||||||
if (previousHasVerifiedEmail !== undefined && hasVerifiedEmail) {
|
if (previousHasVerifiedEmail === false && hasVerifiedEmail) {
|
||||||
analytics.emailVerifiedEvent();
|
analytics.emailVerifiedEvent();
|
||||||
}
|
}
|
||||||
}, [previousHasVerifiedEmail, hasVerifiedEmail]);
|
}, [previousHasVerifiedEmail, hasVerifiedEmail, onSignedIn]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (previousRewardApproved !== undefined && isRewardApproved) {
|
if (previousRewardApproved === false && isRewardApproved) {
|
||||||
analytics.rewardEligibleEvent();
|
analytics.rewardEligibleEvent();
|
||||||
}
|
}
|
||||||
}, [previousRewardApproved, isRewardApproved]);
|
}, [previousRewardApproved, isRewardApproved]);
|
||||||
|
|
||||||
// @if TARGET='web'
|
// Keep this at the end to ensure initial setup effects are run first
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasVerifiedEmail && accessToken) {
|
if (!previousHasVerifiedEmail && hasVerifiedEmail) {
|
||||||
Lbry.setApiHeader('X-Lbry-Auth-Token', accessToken);
|
onSignedIn();
|
||||||
}
|
}
|
||||||
}, [hasVerifiedEmail, accessToken]);
|
}, [previousHasVerifiedEmail, hasVerifiedEmail, onSignedIn]);
|
||||||
// @endif
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={MAIN_WRAPPER_CLASS} ref={appRef} onContextMenu={e => openContextMenu(e)}>
|
<div className={MAIN_WRAPPER_CLASS} ref={appRef} onContextMenu={e => openContextMenu(e)}>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// @flow
|
/* eslint-disable no-undef */
|
||||||
|
/* eslint-disable react/prop-types */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
@ -6,35 +7,33 @@ let scriptLoading = false;
|
||||||
let scriptLoaded = false;
|
let scriptLoaded = false;
|
||||||
let scriptDidError = false;
|
let scriptDidError = false;
|
||||||
|
|
||||||
type Props = {
|
// Flow does not like the way this stripe plugin works
|
||||||
disabled: boolean,
|
// Disabled because it was a huge pain
|
||||||
label: ?string,
|
// type Props = {
|
||||||
email: string,
|
// disabled: boolean,
|
||||||
|
// label: ?string,
|
||||||
|
// email: string,
|
||||||
|
|
||||||
// =====================================================
|
// // =====================================================
|
||||||
// Required by stripe
|
// // Required by stripe
|
||||||
// see Stripe docs for more info:
|
// // see Stripe docs for more info:
|
||||||
// https://stripe.com/docs/checkout#integration-custom
|
// // https://stripe.com/docs/checkout#integration-custom
|
||||||
// =====================================================
|
// // =====================================================
|
||||||
|
|
||||||
// Your publishable key (test or live).
|
// // Your publishable key (test or live).
|
||||||
// can't use "key" as a prop in react, so have to change the keyname
|
// // can't use "key" as a prop in react, so have to change the keyname
|
||||||
stripeKey: string,
|
// stripeKey: string,
|
||||||
|
|
||||||
// The callback to invoke when the Checkout process is complete.
|
// // The callback to invoke when the Checkout process is complete.
|
||||||
// function(token)
|
// // function(token)
|
||||||
// token is the token object created.
|
// // token is the token object created.
|
||||||
// token.id can be used to create a charge or customer.
|
// // token.id can be used to create a charge or customer.
|
||||||
// token.email contains the email address entered by the user.
|
// // token.email contains the email address entered by the user.
|
||||||
token: string,
|
// token: string,
|
||||||
};
|
// };
|
||||||
|
|
||||||
type State = {
|
class CardVerify extends React.Component {
|
||||||
open: boolean,
|
constructor(props) {
|
||||||
};
|
|
||||||
|
|
||||||
class CardVerify extends React.Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
open: false,
|
open: false,
|
||||||
|
@ -87,6 +86,7 @@ class CardVerify extends React.Component<Props, State> {
|
||||||
|
|
||||||
this.loadPromise.promise.then(this.onScriptLoaded).catch(this.onScriptError);
|
this.loadPromise.promise.then(this.onScriptLoaded).catch(this.onScriptError);
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ class CardVerify extends React.Component<Props, State> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
button="inverse"
|
button="primary"
|
||||||
label={this.props.label}
|
label={this.props.label}
|
||||||
disabled={this.props.disabled || this.state.open || this.hasPendingClick}
|
disabled={this.props.disabled || this.state.open || this.hasPendingClick}
|
||||||
onClick={this.onClick.bind(this)}
|
onClick={this.onClick.bind(this)}
|
||||||
|
@ -171,3 +171,5 @@ class CardVerify extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CardVerify;
|
export default CardVerify;
|
||||||
|
/* eslint-enable no-undef */
|
||||||
|
/* eslint-enable react/prop-types */
|
||||||
|
|
|
@ -15,7 +15,6 @@ type Props = {
|
||||||
uris: Array<string>,
|
uris: Array<string>,
|
||||||
header: Node | boolean,
|
header: Node | boolean,
|
||||||
headerAltControls: Node,
|
headerAltControls: Node,
|
||||||
injectedItem?: Node,
|
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
type: string,
|
type: string,
|
||||||
empty?: string,
|
empty?: string,
|
||||||
|
@ -33,7 +32,6 @@ export default function ClaimList(props: Props) {
|
||||||
const {
|
const {
|
||||||
uris,
|
uris,
|
||||||
headerAltControls,
|
headerAltControls,
|
||||||
injectedItem,
|
|
||||||
loading,
|
loading,
|
||||||
persistedStorageKey,
|
persistedStorageKey,
|
||||||
empty,
|
empty,
|
||||||
|
@ -57,7 +55,7 @@ export default function ClaimList(props: Props) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setScrollBottomCbMap({});
|
setScrollBottomCbMap({});
|
||||||
}, [id]);
|
}, [id, setScrollBottomCbMap]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleScroll(e) {
|
function handleScroll(e) {
|
||||||
|
@ -112,8 +110,6 @@ export default function ClaimList(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{injectedItem && <div>{injectedItem}</div>}
|
|
||||||
|
|
||||||
{urisLength > 0 && (
|
{urisLength > 0 && (
|
||||||
<ul className="ul--no-style">
|
<ul className="ul--no-style">
|
||||||
{sortedUris.map((uri, index) => (
|
{sortedUris.map((uri, index) => (
|
||||||
|
@ -121,6 +117,7 @@ export default function ClaimList(props: Props) {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{urisLength === 0 && !loading && <p className="main--empty empty">{empty || __('No results')}</p>}
|
{urisLength === 0 && !loading && <p className="main--empty empty">{empty || __('No results')}</p>}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,13 +32,13 @@ type Props = {
|
||||||
uris: Array<string>,
|
uris: Array<string>,
|
||||||
subscribedChannels: Array<Subscription>,
|
subscribedChannels: Array<Subscription>,
|
||||||
doClaimSearch: ({}) => void,
|
doClaimSearch: ({}) => void,
|
||||||
injectedItem: any,
|
|
||||||
tags: Array<string>,
|
tags: Array<string>,
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
personalView: boolean,
|
personalView: boolean,
|
||||||
doToggleTagFollow: string => void,
|
doToggleTagFollow: string => void,
|
||||||
meta?: Node,
|
meta?: Node,
|
||||||
showNsfw: boolean,
|
showNsfw: boolean,
|
||||||
|
hideCustomization: boolean,
|
||||||
history: { action: string, push: string => void, replace: string => void },
|
history: { action: string, push: string => void, replace: string => void },
|
||||||
location: { search: string, pathname: string },
|
location: { search: string, pathname: string },
|
||||||
claimSearchByQuery: {
|
claimSearchByQuery: {
|
||||||
|
@ -54,21 +54,21 @@ function ClaimListDiscover(props: Props) {
|
||||||
tags,
|
tags,
|
||||||
loading,
|
loading,
|
||||||
personalView,
|
personalView,
|
||||||
injectedItem,
|
|
||||||
meta,
|
meta,
|
||||||
subscribedChannels,
|
subscribedChannels,
|
||||||
showNsfw,
|
showNsfw,
|
||||||
history,
|
history,
|
||||||
location,
|
location,
|
||||||
hiddenUris,
|
hiddenUris,
|
||||||
|
hideCustomization,
|
||||||
} = props;
|
} = props;
|
||||||
const didNavigateForward = history.action === 'PUSH';
|
const didNavigateForward = history.action === 'PUSH';
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
const { search } = location;
|
const { search } = location;
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const personalSort = urlParams.get('sort') || SEARCH_SORT_YOU;
|
const personalSort = urlParams.get('sort') || (hideCustomization ? SEARCH_SORT_ALL : SEARCH_SORT_YOU);
|
||||||
const typeSort = urlParams.get('type') || TYPE_TRENDING;
|
const typeSort = urlParams.get('type') || TYPE_TRENDING;
|
||||||
const timeSort = urlParams.get('time') || TIME_WEEK;
|
const timeSort = urlParams.get('time') || TIME_WEEK;
|
||||||
const [page, setPage] = useState(1);
|
|
||||||
const tagsInUrl = urlParams.get('t') || '';
|
const tagsInUrl = urlParams.get('t') || '';
|
||||||
const options: {
|
const options: {
|
||||||
page_size: number,
|
page_size: number,
|
||||||
|
@ -86,7 +86,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
|
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
|
||||||
// it's faster, but we will need to remove it if we start using total_pages
|
// it's faster, but we will need to remove it if we start using total_pages
|
||||||
no_totals: true,
|
no_totals: true,
|
||||||
any_tags: (personalView && personalSort === SEARCH_SORT_YOU) || !personalView ? tags : [],
|
any_tags: (personalView && !hideCustomization && personalSort === SEARCH_SORT_YOU) || !personalView ? tags : [],
|
||||||
channel_ids: personalSort === SEARCH_SORT_CHANNELS ? subscribedChannels.map(sub => sub.uri.split('#')[1]) : [],
|
channel_ids: personalSort === SEARCH_SORT_CHANNELS ? subscribedChannels.map(sub => sub.uri.split('#')[1]) : [],
|
||||||
not_channel_ids: hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : [],
|
not_channel_ids: hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : [],
|
||||||
not_tags: !showNsfw ? MATURE_TAGS : [],
|
not_tags: !showNsfw ? MATURE_TAGS : [],
|
||||||
|
@ -191,29 +191,33 @@ function ClaimListDiscover(props: Props) {
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</FormField>
|
</FormField>
|
||||||
<span>{__('For')}</span>
|
{!hideCustomization && (
|
||||||
{!personalView && tags && tags.length ? (
|
<Fragment>
|
||||||
tags.map(tag => <Tag key={tag} name={tag} disabled />)
|
<span>{__('For')}</span>
|
||||||
) : (
|
{!personalView && tags && tags.length ? (
|
||||||
<FormField
|
tags.map(tag => <Tag key={tag} name={tag} disabled />)
|
||||||
type="select"
|
) : (
|
||||||
name="trending_overview"
|
<FormField
|
||||||
className="claim-list__dropdown"
|
type="select"
|
||||||
value={personalSort}
|
name="trending_overview"
|
||||||
onChange={e => {
|
className="claim-list__dropdown"
|
||||||
handlePersonalSort(e.target.value);
|
value={personalSort}
|
||||||
}}
|
onChange={e => {
|
||||||
>
|
handlePersonalSort(e.target.value);
|
||||||
{SEARCH_FILTER_TYPES.map(type => (
|
}}
|
||||||
<option key={type} value={type}>
|
>
|
||||||
{type === SEARCH_SORT_ALL
|
{SEARCH_FILTER_TYPES.map(type => (
|
||||||
? __('Everyone')
|
<option key={type} value={type}>
|
||||||
: type === SEARCH_SORT_YOU
|
{type === SEARCH_SORT_ALL
|
||||||
? __('Tags You Follow')
|
? __('Everyone')
|
||||||
: __('Channels You Follow')}
|
: type === SEARCH_SORT_YOU
|
||||||
</option>
|
? __('Tags You Follow')
|
||||||
))}
|
: __('Channels You Follow')}
|
||||||
</FormField>
|
</option>
|
||||||
|
))}
|
||||||
|
</FormField>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
{typeSort === 'top' && (
|
{typeSort === 'top' && (
|
||||||
<FormField
|
<FormField
|
||||||
|
@ -242,7 +246,6 @@ function ClaimListDiscover(props: Props) {
|
||||||
id={claimSearchCacheQuery}
|
id={claimSearchCacheQuery}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
uris={uris}
|
uris={uris}
|
||||||
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}
|
|
||||||
header={header}
|
header={header}
|
||||||
headerAltControls={meta}
|
headerAltControls={meta}
|
||||||
onScrollBottom={handleScrollBottom}
|
onScrollBottom={handleScrollBottom}
|
||||||
|
|
35
src/ui/component/common/card.jsx
Normal file
35
src/ui/component/common/card.jsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// @flow
|
||||||
|
import type { Node } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string | Node,
|
||||||
|
subtitle?: string | Node,
|
||||||
|
body?: string | Node,
|
||||||
|
actions?: string | Node,
|
||||||
|
icon?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Card(props: Props) {
|
||||||
|
const { title, subtitle, body, actions, icon } = props;
|
||||||
|
return (
|
||||||
|
<section className={classnames('card')}>
|
||||||
|
<div className="card__header">
|
||||||
|
<div className="section__flex">
|
||||||
|
{icon && <Icon sectionIcon icon={icon} />}
|
||||||
|
<div>
|
||||||
|
<h2 className="section__title">{title}</h2>
|
||||||
|
<p className="section__subtitle">{subtitle}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{body && <div className={classnames('card__body', { 'card__body--with-icon': icon })}>{body}</div>}
|
||||||
|
{actions && (
|
||||||
|
<div className={classnames('card__main-actions', { 'card__main-actions--with-icon': icon })}>{actions}</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
|
@ -162,11 +162,7 @@ export class FormField extends React.PureComponent<Props> {
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<fieldset-section>
|
<fieldset-section>
|
||||||
<label htmlFor={name}>{errorMessage ? <span className="error-text">{errorMessage}</span> : label}</label>
|
<label htmlFor={name}>{errorMessage ? <span className="error-text">{errorMessage}</span> : label}</label>
|
||||||
{prefix && (
|
{prefix && <label htmlFor={name}>{prefix}</label>}
|
||||||
<label className="form-field--inline-prefix" htmlFor={name}>
|
|
||||||
{prefix}
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
{inner}
|
{inner}
|
||||||
</fieldset-section>
|
</fieldset-section>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
@ -11,6 +11,7 @@ export class Form extends React.PureComponent<Props> {
|
||||||
const { children, onSubmit, ...otherProps } = this.props;
|
const { children, onSubmit, ...otherProps } = this.props;
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
|
noValidate
|
||||||
className="form"
|
className="form"
|
||||||
onSubmit={event => {
|
onSubmit={event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
@ -302,4 +302,9 @@ export const icons = {
|
||||||
<line x1="21" y1="12" x2="9" y2="12" />
|
<line x1="21" y1="12" x2="9" y2="12" />
|
||||||
</g>
|
</g>
|
||||||
),
|
),
|
||||||
|
[ICONS.PHONE]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,6 +17,7 @@ type Props = {
|
||||||
iconColor?: string,
|
iconColor?: string,
|
||||||
size?: number,
|
size?: number,
|
||||||
className?: string,
|
className?: string,
|
||||||
|
sectionIcon?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class IconComponent extends React.PureComponent<Props> {
|
class IconComponent extends React.PureComponent<Props> {
|
||||||
|
@ -49,11 +50,10 @@ class IconComponent extends React.PureComponent<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { icon, tooltip, iconColor, size, className } = this.props;
|
const { icon, tooltip, iconColor, size, className, sectionIcon = false } = this.props;
|
||||||
const Icon = icons[this.props.icon];
|
const Icon = icons[this.props.icon];
|
||||||
|
|
||||||
if (!Icon) {
|
if (!Icon) {
|
||||||
console.error('no icon found for ', icon);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,11 @@ class IconComponent extends React.PureComponent<Props> {
|
||||||
tooltipText = this.getTooltip(icon);
|
tooltipText = this.getTooltip(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inner = <Icon size={iconSize} className={classnames(`icon icon--${icon}`, className)} color={color} />;
|
const component = (
|
||||||
|
<Icon size={sectionIcon ? 20 : iconSize} className={classnames(`icon icon--${icon}`, className)} color={color} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const inner = sectionIcon ? <span className="icon__wrapper">{component}</span> : component;
|
||||||
|
|
||||||
return tooltipText ? <Tooltip label={tooltipText}>{inner}</Tooltip> : inner;
|
return tooltipText ? <Tooltip label={tooltipText}>{inner}</Tooltip> : inner;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectBalance, formatCredits } from 'lbry-redux';
|
import { selectBalance, formatCredits } from 'lbry-redux';
|
||||||
import { selectUserEmail } from 'lbryinc';
|
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
|
import { doSignOut } from 'redux/actions/app';
|
||||||
import Header from './view';
|
import Header from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -13,11 +14,12 @@ const select = state => ({
|
||||||
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
|
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
|
||||||
automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state),
|
automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state),
|
||||||
hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state),
|
hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state),
|
||||||
email: selectUserEmail(state),
|
email: selectUserVerifiedEmail(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||||
|
signOut: () => dispatch(doSignOut()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -12,22 +12,6 @@ import Icon from 'component/common/icon';
|
||||||
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
||||||
import Tooltip from 'component/common/tooltip';
|
import Tooltip from 'component/common/tooltip';
|
||||||
|
|
||||||
// Move this into jessops password util
|
|
||||||
import cookie from 'cookie';
|
|
||||||
// @if TARGET='app'
|
|
||||||
import keytar from 'keytar';
|
|
||||||
// @endif;
|
|
||||||
function deleteAuthToken() {
|
|
||||||
// @if TARGET='app'
|
|
||||||
keytar.deletePassword('LBRY', 'auth_token').catch(console.error); //eslint-disable-line
|
|
||||||
// @endif;
|
|
||||||
// @if TARGET='web'
|
|
||||||
document.cookie = cookie.serialize('auth_token', '', {
|
|
||||||
expires: new Date(),
|
|
||||||
});
|
|
||||||
// @endif
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
autoUpdateDownloaded: boolean,
|
autoUpdateDownloaded: boolean,
|
||||||
balance: string,
|
balance: string,
|
||||||
|
@ -41,6 +25,7 @@ type Props = {
|
||||||
hideBalance: boolean,
|
hideBalance: boolean,
|
||||||
email: ?string,
|
email: ?string,
|
||||||
minimal: boolean,
|
minimal: boolean,
|
||||||
|
signOut: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header = (props: Props) => {
|
const Header = (props: Props) => {
|
||||||
|
@ -53,11 +38,9 @@ const Header = (props: Props) => {
|
||||||
hideBalance,
|
hideBalance,
|
||||||
email,
|
email,
|
||||||
minimal,
|
minimal,
|
||||||
|
signOut,
|
||||||
} = props;
|
} = props;
|
||||||
const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable);
|
const authenticated = Boolean(email);
|
||||||
const isAuthenticated = Boolean(email);
|
|
||||||
// Auth is optional in the desktop app
|
|
||||||
const showFullHeader = IS_WEB ? isAuthenticated : true;
|
|
||||||
|
|
||||||
function handleThemeToggle() {
|
function handleThemeToggle() {
|
||||||
if (automaticDarkModeEnabled) {
|
if (automaticDarkModeEnabled) {
|
||||||
|
@ -71,42 +54,14 @@ const Header = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function signOut() {
|
function getWalletTitle() {
|
||||||
// Replace this with actual clearUser function
|
return hideBalance ? (
|
||||||
window.store.dispatch({ type: 'USER_FETCH_FAILURE' });
|
__('Wallet')
|
||||||
deleteAuthToken();
|
) : (
|
||||||
location.reload();
|
<React.Fragment>
|
||||||
}
|
{roundedBalance} <LbcSymbol />
|
||||||
|
</React.Fragment>
|
||||||
const accountMenuButtons = [
|
);
|
||||||
{
|
|
||||||
path: `/$/account`,
|
|
||||||
icon: ICONS.OVERVIEW,
|
|
||||||
label: __('Overview'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `/$/rewards`,
|
|
||||||
icon: ICONS.FEATURED,
|
|
||||||
label: __('Rewards'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `/$/wallet`,
|
|
||||||
icon: ICONS.WALLET,
|
|
||||||
label: __('Wallet'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `/$/publish`,
|
|
||||||
icon: ICONS.PUBLISH,
|
|
||||||
label: __('Publish'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
accountMenuButtons.push({
|
|
||||||
onClick: signOut,
|
|
||||||
icon: ICONS.PUBLISH,
|
|
||||||
label: __('Publish'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -146,19 +101,17 @@ const Header = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!minimal ? (
|
{!minimal ? (
|
||||||
<div className="header__menu">
|
<div className={classnames('header__menu', { 'header__menu--small': IS_WEB && !authenticated })}>
|
||||||
{showFullHeader ? (
|
{!IS_WEB || authenticated ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton className="header__navigation-item menu__title">
|
<MenuButton className="header__navigation-item menu__title">{getWalletTitle()}</MenuButton>
|
||||||
{roundedBalance} <LbcSymbol />
|
|
||||||
</MenuButton>
|
|
||||||
<MenuList className="menu__list--header">
|
<MenuList className="menu__list--header">
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/wallet`)}>
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.WALLET}`)}>
|
||||||
<Icon aria-hidden icon={ICONS.WALLET} />
|
<Icon aria-hidden icon={ICONS.WALLET} />
|
||||||
{__('Wallet')}
|
{__('Wallet')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/rewards`)}>
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
||||||
<Icon aria-hidden icon={ICONS.FEATURED} />
|
<Icon aria-hidden icon={ICONS.FEATURED} />
|
||||||
{__('Rewards')}
|
{__('Rewards')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -169,19 +122,16 @@ const Header = (props: Props) => {
|
||||||
<Icon size={18} icon={ICONS.ACCOUNT} />
|
<Icon size={18} icon={ICONS.ACCOUNT} />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList className="menu__list--header">
|
<MenuList className="menu__list--header">
|
||||||
<MenuItem
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.ACCOUNT}`)}>
|
||||||
className="menu__link"
|
|
||||||
onSelect={() => history.push(isAuthenticated ? `/$/account` : `/$/auth/signup`)}
|
|
||||||
>
|
|
||||||
<Icon aria-hidden icon={ICONS.OVERVIEW} />
|
<Icon aria-hidden icon={ICONS.OVERVIEW} />
|
||||||
{__('Overview')}
|
{__('Overview')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}>
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISH}`)}>
|
||||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||||
{__('Publish')}
|
{__('Publish')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{isAuthenticated ? (
|
{authenticated ? (
|
||||||
<MenuItem className="menu__link" onSelect={signOut}>
|
<MenuItem className="menu__link" onSelect={signOut}>
|
||||||
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
||||||
{__('Sign Out')}
|
{__('Sign Out')}
|
||||||
|
@ -197,11 +147,11 @@ const Header = (props: Props) => {
|
||||||
<Icon size={18} icon={ICONS.SETTINGS} />
|
<Icon size={18} icon={ICONS.SETTINGS} />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList className="menu__list--header">
|
<MenuList className="menu__list--header">
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/settings`)}>
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
|
||||||
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
|
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
|
||||||
{__('Settings')}
|
{__('Settings')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/help`)}>
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
|
||||||
<Icon aria-hidden icon={ICONS.HELP} />
|
<Icon aria-hidden icon={ICONS.HELP} />
|
||||||
{__('Help')}
|
{__('Help')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -213,10 +163,7 @@ const Header = (props: Props) => {
|
||||||
</Menu>
|
</Menu>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : (
|
) : (
|
||||||
<Fragment>
|
<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Sign In')} />
|
||||||
<span />
|
|
||||||
<Button navigate={`/$/${PAGES.AUTH}/signin`} button="primary" label={__('Sign In')} />
|
|
||||||
</Fragment>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import RewardLink from 'component/rewardLink';
|
import RewardLink from 'component/rewardLink';
|
||||||
import Yrbl from 'component/yrbl';
|
|
||||||
import { rewards } from 'lbryinc';
|
import { rewards } from 'lbryinc';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
|
@ -20,22 +19,10 @@ class InviteList extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { invitees, referralReward } = this.props;
|
const { invitees, referralReward } = this.props;
|
||||||
|
|
||||||
if (!invitees) {
|
if (!invitees || !invitees.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!invitees.length) {
|
|
||||||
return (
|
|
||||||
<Yrbl
|
|
||||||
type="happy"
|
|
||||||
title={__('Power To The People')}
|
|
||||||
subtitle={__(
|
|
||||||
'LBRY is powered by the users. More users, more power… and with great power comes great responsibility.'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let rewardAmount = 0;
|
let rewardAmount = 0;
|
||||||
let rewardHelp = __(
|
let rewardHelp = __(
|
||||||
"Woah, you have a lot of friends! You've claimed the maximum amount of referral rewards. Check back soon to see if more are available!."
|
"Woah, you have a lot of friends! You've claimed the maximum amount of referral rewards. Check back soon to see if more are available!."
|
||||||
|
@ -60,10 +47,10 @@ class InviteList extends React.PureComponent<Props> {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="card__subtitle">{rewardHelp}</p>
|
<p className="section__subtitle">{rewardHelp}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table className="table table--invites">
|
<table className="table section">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{__('Invitee Email')}</th>
|
<th>{__('Invitee Email')}</th>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { Form, FormField } from 'component/common/form';
|
import { Form, FormField } from 'component/common/form';
|
||||||
import CopyableText from 'component/copyableText';
|
import CopyableText from 'component/copyableText';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
|
||||||
type FormProps = {
|
type FormProps = {
|
||||||
inviteNew: string => void,
|
inviteNew: string => void,
|
||||||
|
@ -73,25 +74,28 @@ class InviteNew extends React.PureComponent<Props> {
|
||||||
const { errorMessage, inviteNew, isPending, rewardAmount, referralLink } = this.props;
|
const { errorMessage, inviteNew, isPending, rewardAmount, referralLink } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="card card--section">
|
<Card
|
||||||
<h2 className="card__title">{__('Invite a Friend')}</h2>
|
title={__('Invite a Friend')}
|
||||||
<p className="card__subtitle">{__('When your friends start using LBRY, the network gets stronger!')}</p>
|
subtitle={__('When your friends start using LBRY, the network gets stronger!')}
|
||||||
|
body={
|
||||||
|
<React.Fragment>
|
||||||
|
<FormInviteNew
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
inviteNew={inviteNew}
|
||||||
|
isPending={isPending}
|
||||||
|
rewardAmount={rewardAmount}
|
||||||
|
/>
|
||||||
|
<CopyableText label={__('Or share this link with your friends')} copyable={referralLink} />
|
||||||
|
|
||||||
<FormInviteNew
|
<p className="help">
|
||||||
errorMessage={errorMessage}
|
{__('Earn')} <Button button="link" navigate="/$/rewards" label={__('rewards')} />{' '}
|
||||||
inviteNew={inviteNew}
|
{__('for inviting your friends.')} {__('Read our')}{' '}
|
||||||
isPending={isPending}
|
<Button button="link" label={__('FAQ')} href="https://lbry.com/faq/referrals" />{' '}
|
||||||
rewardAmount={rewardAmount}
|
{__('to learn more about referrals')}.
|
||||||
/>
|
</p>
|
||||||
<CopyableText label={__('Or share this link with your friends')} copyable={referralLink} />
|
</React.Fragment>
|
||||||
|
}
|
||||||
<p className="help">
|
/>
|
||||||
{__('Earn')} <Button button="link" navigate="/$/rewards" label={__('rewards')} />{' '}
|
|
||||||
{__('for inviting your friends.')} {__('Read our')}{' '}
|
|
||||||
<Button button="link" label={__('FAQ')} href="https://lbry.com/faq/referrals" />{' '}
|
|
||||||
{__('to learn more about referrals')}.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||||
import Page from './view';
|
import Page from './view';
|
||||||
|
|
||||||
export default Page;
|
const select = state => ({
|
||||||
|
authenticated: Boolean(selectUserVerifiedEmail(state)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select)(Page);
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import SideBar from 'component/sideBar';
|
import SideBar from 'component/sideBar';
|
||||||
import Header from 'component/header';
|
import Header from 'component/header';
|
||||||
import Button from 'component/button';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: Node | Array<Node>,
|
children: Node | Array<Node>,
|
||||||
|
@ -13,35 +11,19 @@ type Props = {
|
||||||
autoUpdateDownloaded: boolean,
|
autoUpdateDownloaded: boolean,
|
||||||
isUpgradeAvailable: boolean,
|
isUpgradeAvailable: boolean,
|
||||||
fullscreen: boolean,
|
fullscreen: boolean,
|
||||||
doDownloadUpgradeRequested: () => void,
|
authenticated: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function Page(props: Props) {
|
function Page(props: Props) {
|
||||||
const {
|
const { children, className, fullscreen = false, authenticated } = props;
|
||||||
children,
|
const obscureSideBar = IS_WEB ? !authenticated : false;
|
||||||
className,
|
|
||||||
fullscreen = false,
|
|
||||||
autoUpdateDownloaded,
|
|
||||||
isUpgradeAvailable,
|
|
||||||
doDownloadUpgradeRequested,
|
|
||||||
} = props;
|
|
||||||
const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Header minimal={fullscreen} />
|
<Header minimal={fullscreen} />
|
||||||
<div className={classnames('main-wrapper__inner')}>
|
<div className={classnames('main-wrapper__inner')}>
|
||||||
{/* @if TARGET='app' */}
|
<main className={classnames('main', className, { 'main--full-width': fullscreen })}>{children}</main>
|
||||||
{showUpgradeButton && (
|
{!fullscreen && <SideBar obscureSideBar={obscureSideBar} />}
|
||||||
<div className="main__status">
|
|
||||||
{__('Upgrade is ready')}
|
|
||||||
<Button button="alt" icon={ICONS.DOWNLOAD} label={__('Install now')} onClick={doDownloadUpgradeRequested} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* @endif */}
|
|
||||||
|
|
||||||
<main className={classnames('main', className)}>{children}</main>
|
|
||||||
{!fullscreen && <SideBar />}
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,7 +32,7 @@ const RewardLink = (props: Props) => {
|
||||||
|
|
||||||
return !reward ? null : (
|
return !reward ? null : (
|
||||||
<Button
|
<Button
|
||||||
button={button ? 'inverse' : 'link'}
|
button={button ? 'primary' : 'link'}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
label={displayLabel}
|
label={displayLabel}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import CreditAmount from 'component/common/credit-amount';
|
import CreditAmount from 'component/common/credit-amount';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
unclaimedRewardAmount: number,
|
unclaimedRewardAmount: number,
|
||||||
|
@ -14,33 +15,35 @@ class RewardSummary extends React.Component<Props> {
|
||||||
const { unclaimedRewardAmount, fetching } = this.props;
|
const { unclaimedRewardAmount, fetching } = this.props;
|
||||||
const hasRewards = unclaimedRewardAmount > 0;
|
const hasRewards = unclaimedRewardAmount > 0;
|
||||||
return (
|
return (
|
||||||
<section className="card card--section">
|
<Card
|
||||||
<h2 className="card__title">{__('Rewards')}</h2>
|
title={__('Rewards')}
|
||||||
|
subtitle={
|
||||||
<p className="card__subtitle">
|
<React.Fragment>
|
||||||
{fetching && __('You have...')}
|
{fetching && __('You have...')}
|
||||||
{!fetching && hasRewards ? (
|
{!fetching && hasRewards ? (
|
||||||
<I18nMessage
|
<I18nMessage
|
||||||
tokens={{
|
tokens={{
|
||||||
credit_amount: <CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />,
|
credit_amount: <CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
You have %credit_amount% in unclaimed rewards.
|
You have %credit_amount% in unclaimed rewards.
|
||||||
</I18nMessage>
|
</I18nMessage>
|
||||||
) : (
|
) : (
|
||||||
__('You have no rewards available.')
|
__('You have no rewards available.')
|
||||||
)}
|
)}
|
||||||
</p>
|
</React.Fragment>
|
||||||
|
}
|
||||||
<div className="card__actions">
|
actions={
|
||||||
<Button
|
<div className="section__actions">
|
||||||
button="primary"
|
<Button
|
||||||
navigate="/$/rewards"
|
button="primary"
|
||||||
label={hasRewards ? __('Claim Rewards') : __('View Rewards')}
|
navigate="/$/rewards"
|
||||||
/>
|
label={hasRewards ? __('Claim Rewards') : __('View Rewards')}
|
||||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />.
|
/>
|
||||||
</div>
|
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />.
|
||||||
</section>
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import React from 'react';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import RewardLink from 'component/rewardLink';
|
import RewardLink from 'component/rewardLink';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
import Card from 'component/common/card';
|
||||||
import { rewards } from 'lbryinc';
|
import { rewards } from 'lbryinc';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -25,27 +26,28 @@ const RewardTile = (props: Props) => {
|
||||||
const claimed = !!reward.transaction_id;
|
const claimed = !!reward.transaction_id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="card card--section">
|
<Card
|
||||||
<h2 className="card__title">{reward.reward_title}</h2>
|
title={reward.reward_title}
|
||||||
<p className="card__subtitle">{reward.reward_description}</p>
|
subtitle={reward.reward_description}
|
||||||
|
actions={
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
{reward.reward_type === rewards.TYPE_GENERATED_CODE && (
|
{reward.reward_type === rewards.TYPE_GENERATED_CODE && (
|
||||||
<Button button="inverse" onClick={openRewardCodeModal} label={__('Enter Code')} />
|
<Button button="primary" onClick={openRewardCodeModal} label={__('Enter Code')} />
|
||||||
)}
|
)}
|
||||||
{reward.reward_type === rewards.TYPE_REFERRAL && (
|
{reward.reward_type === rewards.TYPE_REFERRAL && (
|
||||||
<Button button="inverse" navigate="/$/invite" label={__('Go To Invites')} />
|
<Button button="primary" navigate="/$/invite" label={__('Go To Invites')} />
|
||||||
)}
|
)}
|
||||||
{reward.reward_type !== rewards.TYPE_REFERRAL &&
|
{reward.reward_type !== rewards.TYPE_REFERRAL &&
|
||||||
(claimed ? (
|
(claimed ? (
|
||||||
<span>
|
<span>
|
||||||
<Icon icon={ICONS.COMPLETED} /> {__('Reward claimed.')}
|
<Icon icon={ICONS.COMPLETED} /> {__('Reward claimed.')}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<RewardLink button reward_type={reward.reward_type} />
|
<RewardLink button reward_type={reward.reward_type} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||||
import { selectScrollStartingPosition } from 'redux/selectors/app';
|
import { selectScrollStartingPosition } from 'redux/selectors/app';
|
||||||
import Router from './view';
|
import Router from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
currentScroll: selectScrollStartingPosition(state),
|
currentScroll: selectScrollStartingPosition(state),
|
||||||
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select)(Router);
|
export default connect(select)(Router);
|
||||||
|
|
|
@ -13,12 +13,10 @@ import RewardsPage from 'page/rewards';
|
||||||
import FileListDownloaded from 'page/fileListDownloaded';
|
import FileListDownloaded from 'page/fileListDownloaded';
|
||||||
import FileListPublished from 'page/fileListPublished';
|
import FileListPublished from 'page/fileListPublished';
|
||||||
import TransactionHistoryPage from 'page/transactionHistory';
|
import TransactionHistoryPage from 'page/transactionHistory';
|
||||||
import AuthPage from 'page/auth';
|
|
||||||
import InvitePage from 'page/invite';
|
import InvitePage from 'page/invite';
|
||||||
import SearchPage from 'page/search';
|
import SearchPage from 'page/search';
|
||||||
import LibraryPage from 'page/library';
|
import LibraryPage from 'page/library';
|
||||||
import WalletPage from 'page/wallet';
|
import WalletPage from 'page/wallet';
|
||||||
import NavigationHistory from 'page/navigationHistory';
|
|
||||||
import TagsPage from 'page/tags';
|
import TagsPage from 'page/tags';
|
||||||
import FollowingPage from 'page/following';
|
import FollowingPage from 'page/following';
|
||||||
import ListBlockedPage from 'page/listBlocked';
|
import ListBlockedPage from 'page/listBlocked';
|
||||||
|
@ -30,9 +28,32 @@ if ('scrollRestoration' in history) {
|
||||||
history.scrollRestoration = 'manual';
|
history.scrollRestoration = 'manual';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PrivateRouteProps = {
|
||||||
|
component: any,
|
||||||
|
isAuthenticated: boolean,
|
||||||
|
location: { pathname: string },
|
||||||
|
};
|
||||||
|
|
||||||
|
function PrivateRoute(props: PrivateRouteProps) {
|
||||||
|
const { component: Component, isAuthenticated, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
{...rest}
|
||||||
|
render={props =>
|
||||||
|
isAuthenticated || !IS_WEB ? (
|
||||||
|
<Component {...props} />
|
||||||
|
) : (
|
||||||
|
<Redirect to={`/$/${PAGES.AUTH}?redirect=${props.location.pathname}`} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
currentScroll: number,
|
currentScroll: number,
|
||||||
location: { pathname: string, search: string },
|
location: { pathname: string, search: string },
|
||||||
|
isAuthenticated: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function AppRouter(props: Props) {
|
function AppRouter(props: Props) {
|
||||||
|
@ -46,25 +67,25 @@ function AppRouter(props: Props) {
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/" exact component={DiscoverPage} />
|
<Route path="/" exact component={DiscoverPage} />
|
||||||
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
|
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
|
||||||
<Route path={`/$/${PAGES.AUTH}`} exact component={AuthPage} />
|
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
|
||||||
<Route path={`/$/${PAGES.AUTH}/signin`} exact component={SignInPage} />
|
|
||||||
<Route path={`/$/${PAGES.INVITE}`} exact component={InvitePage} />
|
|
||||||
<Route path={`/$/${PAGES.DOWNLOADED}`} exact component={FileListDownloaded} />
|
|
||||||
<Route path={`/$/${PAGES.PUBLISHED}`} exact component={FileListPublished} />
|
|
||||||
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
|
||||||
<Route path={`/$/${PAGES.PUBLISH}`} exact component={PublishPage} />
|
|
||||||
<Route path={`/$/${PAGES.REPORT}`} exact component={ReportPage} />
|
|
||||||
<Route path={`/$/${PAGES.REWARDS}`} exact component={RewardsPage} />
|
|
||||||
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
|
||||||
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
|
|
||||||
<Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} />
|
|
||||||
<Route path={`/$/${PAGES.LIBRARY}`} exact component={LibraryPage} />
|
|
||||||
<Route path={`/$/${PAGES.ACCOUNT}`} exact component={AccountPage} />
|
|
||||||
<Route path={`/$/${PAGES.LIBRARY}/all`} exact component={NavigationHistory} />
|
|
||||||
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
|
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
|
||||||
<Route path={`/$/${PAGES.FOLLOWING}`} exact component={FollowingPage} />
|
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
||||||
<Route path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
||||||
<Route path={`/$/${PAGES.BLOCKED}`} exact component={ListBlockedPage} />
|
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.DOWNLOADED}`} component={FileListDownloaded} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISHED}`} component={FileListPublished} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISH}`} component={PublishPage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.REPORT}`} component={ReportPage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} component={RewardsPage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS}`} component={SettingsPage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.TRANSACTIONS}`} component={TransactionHistoryPage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.ACCOUNT}`} component={AccountPage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.FOLLOWING}`} component={FollowingPage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} component={WalletPage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
|
||||||
|
|
||||||
{/* Below need to go at the end to make sure we don't match any of our pages first */}
|
{/* Below need to go at the end to make sure we don't match any of our pages first */}
|
||||||
<Route path="/:claimName" exact component={ShowPage} />
|
<Route path="/:claimName" exact component={ShowPage} />
|
||||||
<Route path="/:claimName/:streamName" exact component={ShowPage} />
|
<Route path="/:claimName/:streamName" exact component={ShowPage} />
|
||||||
|
|
|
@ -3,14 +3,8 @@ import * as ICONS from 'constants/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SEARCH_OPTIONS } from 'lbry-redux';
|
import { SEARCH_OPTIONS } from 'lbry-redux';
|
||||||
import { Form, FormField } from 'component/common/form';
|
import { Form, FormField } from 'component/common/form';
|
||||||
import posed from 'react-pose';
|
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
|
||||||
const ExpandableOptions = posed.div({
|
|
||||||
hide: { height: 0, opacity: 0 },
|
|
||||||
show: { height: 380, opacity: 1 },
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setSearchOption: (string, boolean | string | number) => void,
|
setSearchOption: (string, boolean | string | number) => void,
|
||||||
options: {},
|
options: {},
|
||||||
|
@ -30,93 +24,91 @@ const SearchOptions = (props: Props) => {
|
||||||
iconRight={expanded ? ICONS.UP : ICONS.DOWN}
|
iconRight={expanded ? ICONS.UP : ICONS.DOWN}
|
||||||
onClick={toggleSearchExpanded}
|
onClick={toggleSearchExpanded}
|
||||||
/>
|
/>
|
||||||
<ExpandableOptions pose={expanded ? 'show' : 'hide'}>
|
{expanded && (
|
||||||
{expanded && (
|
<Form className="search__options">
|
||||||
<Form className="search__options">
|
<fieldset>
|
||||||
<fieldset>
|
<legend className="search__legend--1">{__('Search For')}</legend>
|
||||||
<legend className="search__legend--1">{__('Search For')}</legend>
|
{[
|
||||||
{[
|
{
|
||||||
{
|
option: SEARCH_OPTIONS.INCLUDE_FILES,
|
||||||
option: SEARCH_OPTIONS.INCLUDE_FILES,
|
label: __('Files'),
|
||||||
label: __('Files'),
|
},
|
||||||
},
|
{
|
||||||
{
|
option: SEARCH_OPTIONS.INCLUDE_CHANNELS,
|
||||||
option: SEARCH_OPTIONS.INCLUDE_CHANNELS,
|
label: __('Channels'),
|
||||||
label: __('Channels'),
|
},
|
||||||
},
|
{
|
||||||
{
|
option: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
|
||||||
option: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
|
label: __('Everything'),
|
||||||
label: __('Everything'),
|
},
|
||||||
},
|
].map(({ option, label }) => (
|
||||||
].map(({ option, label }) => (
|
|
||||||
<FormField
|
|
||||||
key={option}
|
|
||||||
name={option}
|
|
||||||
type="radio"
|
|
||||||
blockWrap={false}
|
|
||||||
label={label}
|
|
||||||
checked={options[SEARCH_OPTIONS.CLAIM_TYPE] === option}
|
|
||||||
onChange={() => setSearchOption(SEARCH_OPTIONS.CLAIM_TYPE, option)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend className="search__legend--2">{__('File Types')}</legend>
|
|
||||||
{[
|
|
||||||
{
|
|
||||||
option: SEARCH_OPTIONS.MEDIA_VIDEO,
|
|
||||||
label: __('Videos'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
option: SEARCH_OPTIONS.MEDIA_AUDIO,
|
|
||||||
label: __('Audio'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
option: SEARCH_OPTIONS.MEDIA_IMAGE,
|
|
||||||
label: __('Images'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
option: SEARCH_OPTIONS.MEDIA_TEXT,
|
|
||||||
label: __('Text'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
option: SEARCH_OPTIONS.MEDIA_APPLICATION,
|
|
||||||
label: __('Other Files'),
|
|
||||||
},
|
|
||||||
].map(({ option, label }) => (
|
|
||||||
<FormField
|
|
||||||
key={option}
|
|
||||||
name={option}
|
|
||||||
type="checkbox"
|
|
||||||
blockWrap={false}
|
|
||||||
disabled={options[SEARCH_OPTIONS.CLAIM_TYPE] === SEARCH_OPTIONS.INCLUDE_CHANNELS}
|
|
||||||
label={label}
|
|
||||||
checked={options[option]}
|
|
||||||
onChange={() => setSearchOption(option, !options[option])}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend className="search__legend--3">{__('Other Options')}</legend>
|
|
||||||
<FormField
|
<FormField
|
||||||
type="select"
|
key={option}
|
||||||
name="result-count"
|
name={option}
|
||||||
value={resultCount}
|
type="radio"
|
||||||
onChange={e => setSearchOption(SEARCH_OPTIONS.RESULT_COUNT, e.target.value)}
|
|
||||||
blockWrap={false}
|
blockWrap={false}
|
||||||
label={__('Returned Results')}
|
label={label}
|
||||||
>
|
checked={options[SEARCH_OPTIONS.CLAIM_TYPE] === option}
|
||||||
<option value={10}>10</option>
|
onChange={() => setSearchOption(SEARCH_OPTIONS.CLAIM_TYPE, option)}
|
||||||
<option value={30}>30</option>
|
/>
|
||||||
<option value={50}>50</option>
|
))}
|
||||||
<option value={100}>100</option>
|
</fieldset>
|
||||||
</FormField>
|
|
||||||
</fieldset>
|
<fieldset>
|
||||||
</Form>
|
<legend className="search__legend--2">{__('File Types')}</legend>
|
||||||
)}
|
{[
|
||||||
</ExpandableOptions>
|
{
|
||||||
|
option: SEARCH_OPTIONS.MEDIA_VIDEO,
|
||||||
|
label: __('Videos'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
option: SEARCH_OPTIONS.MEDIA_AUDIO,
|
||||||
|
label: __('Audio'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
option: SEARCH_OPTIONS.MEDIA_IMAGE,
|
||||||
|
label: __('Images'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
option: SEARCH_OPTIONS.MEDIA_TEXT,
|
||||||
|
label: __('Text'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
option: SEARCH_OPTIONS.MEDIA_APPLICATION,
|
||||||
|
label: __('Other Files'),
|
||||||
|
},
|
||||||
|
].map(({ option, label }) => (
|
||||||
|
<FormField
|
||||||
|
key={option}
|
||||||
|
name={option}
|
||||||
|
type="checkbox"
|
||||||
|
blockWrap={false}
|
||||||
|
disabled={options[SEARCH_OPTIONS.CLAIM_TYPE] === SEARCH_OPTIONS.INCLUDE_CHANNELS}
|
||||||
|
label={label}
|
||||||
|
checked={options[option]}
|
||||||
|
onChange={() => setSearchOption(option, !options[option])}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend className="search__legend--3">{__('Other Options')}</legend>
|
||||||
|
<FormField
|
||||||
|
type="select"
|
||||||
|
name="result-count"
|
||||||
|
value={resultCount}
|
||||||
|
onChange={e => setSearchOption(SEARCH_OPTIONS.RESULT_COUNT, e.target.value)}
|
||||||
|
blockWrap={false}
|
||||||
|
label={__('Returned Results')}
|
||||||
|
>
|
||||||
|
<option value={10}>10</option>
|
||||||
|
<option value={30}>30</option>
|
||||||
|
<option value={50}>50</option>
|
||||||
|
<option value={100}>100</option>
|
||||||
|
</FormField>
|
||||||
|
</fieldset>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
channel: string, // currently selected channel
|
channel: string, // currently selected channel
|
||||||
channels: Array<{ name: string }>,
|
channels: ?Array<{ name: string }>,
|
||||||
balance: number,
|
balance: number,
|
||||||
onChannelChange: string => void,
|
onChannelChange: string => void,
|
||||||
createChannel: (string, number) => Promise<any>,
|
createChannel: (string, number) => Promise<any>,
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React, { Fragment } from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Tag from 'component/tag';
|
import Tag from 'component/tag';
|
||||||
import StickyBox from 'react-sticky-box/dist/esnext';
|
import StickyBox from 'react-sticky-box/dist/esnext';
|
||||||
|
import 'css-doodle';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
subscriptions: Array<Subscription>,
|
subscriptions: Array<Subscription>,
|
||||||
followedTags: Array<Tag>,
|
followedTags: Array<Tag>,
|
||||||
email: ?string,
|
email: ?string,
|
||||||
|
obscureSideBar: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function SideBar(props: Props) {
|
function SideBar(props: Props) {
|
||||||
const { subscriptions, followedTags, email } = props;
|
const { subscriptions, followedTags, obscureSideBar } = props;
|
||||||
const showSideBar = IS_WEB ? Boolean(email) : true;
|
|
||||||
|
|
||||||
function buildLink(path, label, icon, guide) {
|
function buildLink(path, label, icon, guide) {
|
||||||
return {
|
return {
|
||||||
navigate: path ? `$/${path}` : '/',
|
navigate: path ? `$/${path}` : '/',
|
||||||
|
@ -25,68 +25,67 @@ function SideBar(props: Props) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return obscureSideBar ? (
|
||||||
<StickyBox offsetTop={100} offsetBottom={20}>
|
<StickyBox offsetTop={100} offsetBottom={20}>
|
||||||
{showSideBar ? (
|
<div className="card navigation--placeholder">
|
||||||
<nav className="navigation">
|
<div className="wrap">
|
||||||
<ul className="navigation-links">
|
<h2>LBRY</h2>
|
||||||
{[
|
|
||||||
{
|
|
||||||
...buildLink(null, __('Home'), ICONS.HOME),
|
|
||||||
},
|
|
||||||
// @if TARGET='app'
|
|
||||||
{
|
|
||||||
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
|
|
||||||
},
|
|
||||||
// @endif
|
|
||||||
{
|
|
||||||
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH),
|
|
||||||
},
|
|
||||||
].map(linkProps => (
|
|
||||||
<li key={linkProps.label}>
|
|
||||||
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{email ? (
|
<p>{__('The best decentralized content platform on the web.')}</p>
|
||||||
<Fragment>
|
<div className="card__actions">{/* <Button button="primary" label={__('Do Something')} /> */}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StickyBox>
|
||||||
|
) : (
|
||||||
|
<StickyBox offsetTop={100} offsetBottom={20}>
|
||||||
|
<nav className="navigation">
|
||||||
|
<ul className="navigation-links">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
...buildLink(null, __('Home'), ICONS.HOME),
|
||||||
|
},
|
||||||
|
// @if TARGET='app'
|
||||||
|
{
|
||||||
|
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
|
||||||
|
},
|
||||||
|
// @endif
|
||||||
|
{
|
||||||
|
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH),
|
||||||
|
},
|
||||||
|
].map(linkProps => (
|
||||||
|
<li key={linkProps.label}>
|
||||||
|
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
navigate={`/$/${PAGES.FOLLOWING}`}
|
||||||
|
label={__('Customize')}
|
||||||
|
icon={ICONS.EDIT}
|
||||||
|
className="navigation-link"
|
||||||
|
activeClass="navigation-link--active"
|
||||||
|
/>
|
||||||
|
<ul className="navigation-links tags--vertical">
|
||||||
|
{followedTags.map(({ name }, key) => (
|
||||||
|
<li className="navigation-link__wrapper" key={name}>
|
||||||
|
<Tag navigate={`/$/tags?t${name}`} name={name} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<ul className="navigation-links--small">
|
||||||
|
{subscriptions.map(({ uri, channelName }, index) => (
|
||||||
|
<li key={uri} className="navigation-link__wrapper">
|
||||||
<Button
|
<Button
|
||||||
navigate={`/$/${PAGES.FOLLOWING}`}
|
navigate={uri}
|
||||||
label={__('Customize')}
|
label={channelName}
|
||||||
icon={ICONS.EDIT}
|
|
||||||
className="navigation-link"
|
className="navigation-link"
|
||||||
activeClass="navigation-link--active"
|
activeClass="navigation-link--active"
|
||||||
/>
|
/>
|
||||||
<ul className="navigation-links tags--vertical">
|
</li>
|
||||||
{followedTags.map(({ name }, key) => (
|
))}
|
||||||
<li className="navigation-link__wrapper" key={name}>
|
</ul>
|
||||||
<Tag navigate={`/$/tags?t${name}`} name={name} />
|
</nav>
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
<ul className="navigation-links--small">
|
|
||||||
{subscriptions.map(({ uri, channelName }, index) => (
|
|
||||||
<li key={uri} className="navigation-link__wrapper">
|
|
||||||
<Button
|
|
||||||
navigate={uri}
|
|
||||||
label={channelName}
|
|
||||||
className="navigation-link"
|
|
||||||
activeClass="navigation-link--active"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</Fragment>
|
|
||||||
) : (
|
|
||||||
<div className="navigation--placeholder" style={{ height: '20vh', marginTop: '1rem', padding: '1rem' }}>
|
|
||||||
Something about logging up to customize here
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</nav>
|
|
||||||
) : (
|
|
||||||
<div className="navigation--placeholder" />
|
|
||||||
)}
|
|
||||||
</StickyBox>
|
</StickyBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,30 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTransition, animated } from 'react-spring';
|
|
||||||
import { Form, FormField } from 'component/common/form';
|
import { Form, FormField } from 'component/common/form';
|
||||||
import Tag from 'component/tag';
|
import Tag from 'component/tag';
|
||||||
|
|
||||||
const unfollowedTagsAnimation = {
|
|
||||||
from: { opacity: 0 },
|
|
||||||
enter: { opacity: 1, maxWidth: 200 },
|
|
||||||
leave: { opacity: 0, maxWidth: 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
tagsPasssedIn: Array<Tag>,
|
||||||
unfollowedTags: Array<Tag>,
|
unfollowedTags: Array<Tag>,
|
||||||
followedTags: Array<Tag>,
|
followedTags: Array<Tag>,
|
||||||
doToggleTagFollow: string => void,
|
doToggleTagFollow: string => void,
|
||||||
doAddTag: string => void,
|
doAddTag: string => void,
|
||||||
onSelect?: Tag => void,
|
onSelect?: Tag => void,
|
||||||
suggestMature?: boolean,
|
suggestMature?: boolean,
|
||||||
|
onRemove: Tag => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TagSelect(props: Props) {
|
export default function TagSelect(props: Props) {
|
||||||
const { unfollowedTags = [], followedTags = [], doToggleTagFollow, doAddTag, onSelect, suggestMature } = props;
|
const {
|
||||||
|
tagsPasssedIn,
|
||||||
|
unfollowedTags = [],
|
||||||
|
followedTags = [],
|
||||||
|
doToggleTagFollow,
|
||||||
|
doAddTag,
|
||||||
|
onSelect,
|
||||||
|
onRemove,
|
||||||
|
suggestMature,
|
||||||
|
} = props;
|
||||||
const [newTag, setNewTag] = useState('');
|
const [newTag, setNewTag] = useState('');
|
||||||
|
|
||||||
let tags = unfollowedTags.slice();
|
let tags = unfollowedTags.slice();
|
||||||
|
@ -39,8 +43,6 @@ export default function TagSelect(props: Props) {
|
||||||
suggestedTags.push('mature');
|
suggestedTags.push('mature');
|
||||||
}
|
}
|
||||||
|
|
||||||
const suggestedTransitions = useTransition(suggestedTags, tag => tag, unfollowedTagsAnimation);
|
|
||||||
|
|
||||||
function onChange(e) {
|
function onChange(e) {
|
||||||
setNewTag(e.target.value);
|
setNewTag(e.target.value);
|
||||||
}
|
}
|
||||||
|
@ -76,24 +78,37 @@ export default function TagSelect(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<React.Fragment>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form className="tags__input-wrapper" onSubmit={handleSubmit}>
|
||||||
<FormField
|
<ul className="tags--remove">
|
||||||
label={__('Find New Tags')}
|
{tagsPasssedIn.map(tag => (
|
||||||
onChange={onChange}
|
<Tag
|
||||||
placeholder={__('Search for more tags')}
|
key={tag.name}
|
||||||
type="text"
|
name={tag.name}
|
||||||
value={newTag}
|
type="remove"
|
||||||
/>
|
onClick={() => {
|
||||||
|
onRemove(tag);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<li>
|
||||||
|
<FormField
|
||||||
|
autoFocus
|
||||||
|
className="tag__input"
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder={__('Follow more tags')}
|
||||||
|
type="text"
|
||||||
|
value={newTag}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</Form>
|
</Form>
|
||||||
<ul className="tags">
|
<ul className="tags">
|
||||||
{suggestedTransitions.map(({ item, key, props }) => (
|
{suggestedTags.map(tag => (
|
||||||
<animated.li key={key} style={props}>
|
<Tag key={tag} name={tag} type="add" onClick={() => handleTagClick(tag)} />
|
||||||
<Tag name={item} type="add" onClick={() => handleTagClick(item)} />
|
|
||||||
</animated.li>
|
|
||||||
))}
|
))}
|
||||||
{!suggestedTransitions.length && <p className="empty tags__empty-message">No suggested tags</p>}
|
{!suggestedTags.length && <p className="empty tags__empty-message">No suggested tags</p>}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React from 'react';
|
import * as React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Tag from 'component/tag';
|
import Tag from 'component/tag';
|
||||||
import TagsSearch from 'component/tagsSearch';
|
import TagsSearch from 'component/tagsSearch';
|
||||||
import usePersistedState from 'util/use-persisted-state';
|
import usePersistedState from 'util/use-persisted-state';
|
||||||
import { useTransition, animated } from 'react-spring';
|
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
showClose?: boolean,
|
showClose?: boolean,
|
||||||
|
@ -17,17 +17,9 @@ type Props = {
|
||||||
// The default component is for following tags
|
// The default component is for following tags
|
||||||
title?: string | boolean,
|
title?: string | boolean,
|
||||||
help?: string,
|
help?: string,
|
||||||
empty?: string,
|
|
||||||
tagsChosen?: Array<Tag>,
|
tagsChosen?: Array<Tag>,
|
||||||
onSelect?: Tag => void,
|
onSelect?: Tag => void,
|
||||||
onRemove?: Tag => void,
|
onRemove?: Tag => void,
|
||||||
className?: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
const tagsAnimation = {
|
|
||||||
from: { opacity: 0 },
|
|
||||||
enter: { opacity: 1, maxWidth: 400 },
|
|
||||||
leave: { opacity: 0, maxWidth: 0 },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TagSelect(props: Props) {
|
export default function TagSelect(props: Props) {
|
||||||
|
@ -37,16 +29,14 @@ export default function TagSelect(props: Props) {
|
||||||
doToggleTagFollow = null,
|
doToggleTagFollow = null,
|
||||||
title,
|
title,
|
||||||
help,
|
help,
|
||||||
empty,
|
|
||||||
tagsChosen,
|
tagsChosen,
|
||||||
onSelect,
|
onSelect,
|
||||||
onRemove,
|
onRemove,
|
||||||
suggestMature,
|
suggestMature,
|
||||||
className,
|
|
||||||
} = props;
|
} = props;
|
||||||
const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false);
|
const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false);
|
||||||
const tagsToDisplay = tagsChosen || followedTags;
|
const tagsToDisplay = tagsChosen || followedTags;
|
||||||
const transitions = useTransition(tagsToDisplay, tag => tag.name, tagsAnimation);
|
const tagCount = tagsToDisplay.length;
|
||||||
const hasMatureTag = tagsToDisplay.map(tag => tag.name).includes('mature');
|
const hasMatureTag = tagsToDisplay.map(tag => tag.name).includes('mature');
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
|
@ -65,40 +55,43 @@ export default function TagSelect(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (tagCount === 0) {
|
||||||
|
setHasClosed(false);
|
||||||
|
}
|
||||||
|
}, [tagCount, setHasClosed]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
((showClose && !hasClosed) || !showClose) && (
|
((showClose && !hasClosed) || !showClose) && (
|
||||||
<div className={className}>
|
<Card
|
||||||
{title !== false && (
|
icon={ICONS.TAG}
|
||||||
<h2 className="card__title">
|
title={
|
||||||
|
<React.Fragment>
|
||||||
{title}
|
{title}
|
||||||
{showClose && !hasClosed && <Button button="close" icon={ICONS.REMOVE} onClick={handleClose} />}
|
{showClose && tagsToDisplay.length > 0 && !hasClosed && (
|
||||||
</h2>
|
<Button button="close" icon={ICONS.REMOVE} onClick={handleClose} />
|
||||||
)}
|
)}
|
||||||
|
</React.Fragment>
|
||||||
<ul className="tags--remove">
|
}
|
||||||
{transitions.map(({ item, props, key }) => (
|
body={
|
||||||
<animated.li key={key} style={props}>
|
<React.Fragment>
|
||||||
<Tag
|
<section className="section">
|
||||||
name={item.name}
|
<TagsSearch
|
||||||
type="remove"
|
onRemove={handleTagClick}
|
||||||
onClick={() => {
|
onSelect={onSelect}
|
||||||
handleTagClick(item);
|
suggestMature={suggestMature && !hasMatureTag}
|
||||||
}}
|
tagsPasssedIn={tagsToDisplay}
|
||||||
/>
|
/>
|
||||||
</animated.li>
|
{help !== false && (
|
||||||
))}
|
<p className="help">
|
||||||
{!transitions.length && (
|
{help || __("The tags you follow will change what's trending for you.")}{' '}
|
||||||
<div className="empty">{empty || __("You aren't following any tags, try searching for one.")}</div>
|
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/trending" />.
|
||||||
)}
|
</p>
|
||||||
</ul>
|
)}
|
||||||
<TagsSearch onSelect={onSelect} suggestMature={suggestMature && !hasMatureTag} />
|
</section>
|
||||||
{help !== false && (
|
</React.Fragment>
|
||||||
<p className="help">
|
}
|
||||||
{help || __("The tags you follow will change what's trending for you.")}{' '}
|
/>
|
||||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/trending" />.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,8 @@ class TransactionListRecent extends React.PureComponent<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { fetchMyClaims, fetchTransactions } = this.props;
|
const { fetchMyClaims, fetchTransactions } = this.props;
|
||||||
|
|
||||||
// @if TARGET='app'
|
|
||||||
fetchMyClaims();
|
fetchMyClaims();
|
||||||
fetchTransactions();
|
fetchTransactions();
|
||||||
// @endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import UserEmailNew from 'component/userEmailNew';
|
import UserSignOutButton from 'component/userSignOutButton';
|
||||||
import UserEmailVerify from 'component/userEmailVerify';
|
import Card from 'component/common/card';
|
||||||
import UserEmailResetButton from 'component/userEmailResetButton';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cancelButton: Node,
|
cancelButton: Node,
|
||||||
|
@ -34,44 +34,34 @@ function UserEmail(props: Props) {
|
||||||
}, [accessToken, fetchAccessToken]);
|
}, [accessToken, fetchAccessToken]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="card card--section">
|
<Card
|
||||||
{!email && <UserEmailNew />}
|
title={__('Email')}
|
||||||
{user && email && !isVerified && <UserEmailVerify />}
|
subtitle={__(
|
||||||
{email && isVerified && (
|
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to save account information and earn rewards.'
|
||||||
<React.Fragment>
|
|
||||||
<h2 className="card__title">{__('Email')}</h2>
|
|
||||||
<p className="card__subtitle">
|
|
||||||
{email && isVerified && __('Your email has been successfully verified')}
|
|
||||||
{!email && __('')}.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{isVerified && (
|
|
||||||
<FormField
|
|
||||||
type="text"
|
|
||||||
className="form-field--copyable"
|
|
||||||
readOnly
|
|
||||||
label={
|
|
||||||
<React.Fragment>
|
|
||||||
{__('Your Email')}{' '}
|
|
||||||
<Button
|
|
||||||
button="link"
|
|
||||||
label={__('Update mailing preferences')}
|
|
||||||
href={`http://lbry.io/list/edit/${accessToken}`}
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
}
|
|
||||||
value={email}
|
|
||||||
inputButton={<UserEmailResetButton button="inverse" />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<p className="help">
|
|
||||||
{`${__(
|
|
||||||
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to save account information and earn rewards.'
|
|
||||||
)} `}
|
|
||||||
</p>
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
)}
|
||||||
</section>
|
body={
|
||||||
|
isVerified ? (
|
||||||
|
<FormField
|
||||||
|
type="text"
|
||||||
|
className="form-field--copyable"
|
||||||
|
readOnly
|
||||||
|
label={
|
||||||
|
<React.Fragment>
|
||||||
|
{__('Your Email')}{' '}
|
||||||
|
<Button
|
||||||
|
button="link"
|
||||||
|
label={__('Update mailing preferences')}
|
||||||
|
href={`http://lbry.io/list/edit/${accessToken}`}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
inputButton={<UserSignOutButton button="inverse" />}
|
||||||
|
value={email || ''}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
actions={!isVerified ? <Button button="primary" label={__('Add Email')} navigate={`/$/${PAGES.AUTH}`} /> : null}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,14 @@ type Props = {
|
||||||
addUserEmail: string => void,
|
addUserEmail: string => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// "Email regex that 99.99% works"
|
||||||
|
// https://emailregex.com/
|
||||||
|
const EMAIL_REGEX = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
|
||||||
|
|
||||||
function UserEmailNew(props: Props) {
|
function UserEmailNew(props: Props) {
|
||||||
const { errorMessage, isPending, addUserEmail } = props;
|
const { errorMessage, isPending, addUserEmail } = props;
|
||||||
const [newEmail, setEmail] = useState('');
|
const [newEmail, setEmail] = useState('');
|
||||||
const [sync, setSync] = useState(false);
|
const valid = newEmail.match(EMAIL_REGEX);
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
addUserEmail(newEmail);
|
addUserEmail(newEmail);
|
||||||
|
@ -26,26 +30,26 @@ function UserEmailNew(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit}>
|
<div>
|
||||||
<FormField
|
<h1 className="section__title--large">{__('Welcome To LBRY')}</h1>
|
||||||
type="email"
|
<p className="section__subtitle">{__('Create a new account or sign in.')}</p>
|
||||||
id="sign_up_email"
|
<Form onSubmit={handleSubmit} className="section__body">
|
||||||
label={__('Email')}
|
<FormField
|
||||||
value={newEmail}
|
autoFocus
|
||||||
error={errorMessage}
|
className="form-field--short"
|
||||||
onChange={e => setEmail(e.target.value)}
|
placeholder={__('hotstuff_96@hotmail.com')}
|
||||||
/>
|
type="email"
|
||||||
<FormField
|
id="sign_up_email"
|
||||||
type="checkbox"
|
label={__('Email')}
|
||||||
id="sign_up_sync"
|
value={newEmail}
|
||||||
label={__('Sync my bidnezz on this device')}
|
error={errorMessage}
|
||||||
helper={__('Maybe some additional text with this field')}
|
onChange={e => setEmail(e.target.value)}
|
||||||
checked={sync}
|
/>
|
||||||
onChange={() => setSync(!sync)}
|
<div className="card__actions">
|
||||||
/>
|
<Button button="primary" type="submit" label={__('Continue')} disabled={!newEmail || !valid || isPending} />
|
||||||
|
</div>
|
||||||
<Button button="primary" type="submit" label={__('Continue')} disabled={isPending} />
|
</Form>
|
||||||
</Form>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import UserEmailResetButton from './view';
|
|
||||||
|
|
||||||
const select = state => ({});
|
|
||||||
|
|
||||||
const perform = dispatch => ({});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(UserEmailResetButton);
|
|
|
@ -1,24 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from 'react';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import cookie from 'cookie';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
button: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
function UserEmailResetButton(props: Props) {
|
|
||||||
const { button = 'link' } = props;
|
|
||||||
const buttonsProps = IS_WEB
|
|
||||||
? {
|
|
||||||
onClick: () => {
|
|
||||||
document.cookie = cookie.serialize('auth_token', '');
|
|
||||||
window.location.reload();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: { href: 'https://lbry.com/faq/how-to-change-email' };
|
|
||||||
|
|
||||||
return <Button button={button} label={__('Change... fix me sean')} {...buttonsProps} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UserEmailResetButton;
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import UserEmailResetButton from 'component/userEmailResetButton';
|
import UserSignOutButton from 'component/userSignOutButton';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
email: string,
|
email: string,
|
||||||
|
@ -52,18 +52,19 @@ class UserEmailVerify extends React.PureComponent<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<p className="card__subtitle">
|
<h1 className="section__title--large">{__('Confirm Your Email')}</h1>
|
||||||
{__('An email was sent to')} {email}.{' '}
|
|
||||||
{__('Follow the link and you will be good to go. This will update automatically.')}
|
<p className="section__subtitle">
|
||||||
|
{__('An email was sent to')} {email}. {__('Follow the link to sign in. This will update automatically.')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="card__actions">
|
<div className="section__body section__actions">
|
||||||
<Button
|
<Button
|
||||||
button="primary"
|
button="primary"
|
||||||
label={__('Resend Verification Email')}
|
label={__('Resend Verification Email')}
|
||||||
onClick={this.handleResendVerificationEmail}
|
onClick={this.handleResendVerificationEmail}
|
||||||
/>
|
/>
|
||||||
<UserEmailResetButton />
|
<UserSignOutButton label={__('Start Over')} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="help">
|
<p className="help">
|
||||||
|
|
|
@ -1,28 +1,21 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectUser, selectEmailToVerify, rewards as REWARD_TYPES, doClaimRewardType } from 'lbryinc';
|
import { selectUser, selectEmailToVerify } from 'lbryinc';
|
||||||
import { doCreateChannel, selectCreatingChannel, selectMyChannelClaims } from 'lbry-redux';
|
import { doCreateChannel, selectCreatingChannel, selectMyChannelClaims, selectCreateChannelError } from 'lbry-redux';
|
||||||
import UserSignUp from './view';
|
import UserFirstChannel from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
email: selectEmailToVerify(state),
|
email: selectEmailToVerify(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
creatingChannel: selectCreatingChannel(state),
|
|
||||||
channels: selectMyChannelClaims(state),
|
channels: selectMyChannelClaims(state),
|
||||||
|
creatingChannel: selectCreatingChannel(state),
|
||||||
|
createChannelError: selectCreateChannelError(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
|
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
|
||||||
claimReward: cb =>
|
|
||||||
dispatch(
|
|
||||||
doClaimRewardType(REWARD_TYPES.TYPE_REWARD_CODE, {
|
|
||||||
notifyError: true,
|
|
||||||
successCallback: cb,
|
|
||||||
params: { code: 'sean-test' },
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
select,
|
select,
|
||||||
perform
|
perform
|
||||||
)(UserSignUp);
|
)(UserFirstChannel);
|
||||||
|
|
|
@ -4,23 +4,25 @@ import { isNameValid } from 'lbry-redux';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { Form, FormField } from 'component/common/form';
|
import { Form, FormField } from 'component/common/form';
|
||||||
|
|
||||||
const DEFAULT_BID_FOR_FIRST_CHANNEL = 0.1;
|
export const DEFAULT_BID_FOR_FIRST_CHANNEL = 0.9;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
createChannel: (string, number) => void,
|
createChannel: (string, number) => void,
|
||||||
claimReward: (() => void) => void,
|
|
||||||
creatingChannel: boolean,
|
creatingChannel: boolean,
|
||||||
|
createChannelError: string,
|
||||||
|
claimingReward: boolean,
|
||||||
|
user: User,
|
||||||
};
|
};
|
||||||
|
|
||||||
function UserFirstChannel(props: Props) {
|
function UserFirstChannel(props: Props) {
|
||||||
const { createChannel, creatingChannel, claimReward } = props;
|
const { createChannel, creatingChannel, claimingReward, user, createChannelError } = props;
|
||||||
const [channel, setChannel] = useState('');
|
const { primary_email: primaryEmail } = user;
|
||||||
const [nameError, setNameError] = useState();
|
const initialChannel = primaryEmail.split('@')[0];
|
||||||
|
const [channel, setChannel] = useState(initialChannel);
|
||||||
|
const [nameError, setNameError] = useState(undefined);
|
||||||
|
|
||||||
function handleCreateChannel() {
|
function handleCreateChannel() {
|
||||||
claimReward(() => {
|
createChannel(`@${channel}`, DEFAULT_BID_FOR_FIRST_CHANNEL);
|
||||||
createChannel(`@${channel}`, DEFAULT_BID_FOR_FIRST_CHANNEL);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChannelChange(e) {
|
function handleChannelChange(e) {
|
||||||
|
@ -35,29 +37,43 @@ function UserFirstChannel(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleCreateChannel}>
|
<Form onSubmit={handleCreateChannel}>
|
||||||
<h1 className="card__title--large">{__('Choose Your Channel Name')}</h1>
|
<h1 className="section__title--large">{__('Create A Channel')}</h1>
|
||||||
<p className="card__subtitle">
|
<div className="section__subtitle">
|
||||||
{__("Normally this would cost LBRY credits, but we're hooking you up for your first one.")}
|
<p>{__('A channel is your identity on the LBRY network.')}</p>
|
||||||
</p>
|
<p>{__('You can have more than one or change this later.')}</p>
|
||||||
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
|
||||||
<fieldset-section>
|
|
||||||
<label htmlFor="auth_first_channel">
|
|
||||||
{nameError ? <span className="error-text">{nameError}</span> : __('Your Channel')}
|
|
||||||
</label>
|
|
||||||
<div className="form-field__prefix">@</div>
|
|
||||||
</fieldset-section>
|
|
||||||
|
|
||||||
<FormField type="text" name="auth_first_channel" value={channel} onChange={handleChannelChange} />
|
|
||||||
</fieldset-group>
|
|
||||||
<div className="card__actions">
|
|
||||||
<Button
|
|
||||||
button="primary"
|
|
||||||
type="submit"
|
|
||||||
disabled={nameError || !channel || creatingChannel}
|
|
||||||
label={__('Create')}
|
|
||||||
onClick={handleCreateChannel}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<section className="section__body">
|
||||||
|
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
||||||
|
<fieldset-section>
|
||||||
|
<label htmlFor="auth_first_channel">
|
||||||
|
{createChannelError || nameError ? (
|
||||||
|
<span className="error-text">{createChannelError || nameError}</span>
|
||||||
|
) : (
|
||||||
|
__('Your Channel')
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<div className="form-field__prefix">@</div>
|
||||||
|
</fieldset-section>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
autoFocus
|
||||||
|
placeholder={__('channel')}
|
||||||
|
type="text"
|
||||||
|
name="auth_first_channel"
|
||||||
|
className="form-field--short"
|
||||||
|
value={channel}
|
||||||
|
onChange={handleChannelChange}
|
||||||
|
/>
|
||||||
|
</fieldset-group>
|
||||||
|
<div className="section__actions">
|
||||||
|
<Button
|
||||||
|
button="primary"
|
||||||
|
type="submit"
|
||||||
|
disabled={nameError || !channel || creatingChannel || claimingReward}
|
||||||
|
label={creatingChannel || claimingReward ? __('Creating') : __('Create')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,39 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
selectEmailToVerify,
|
selectEmailToVerify,
|
||||||
doUserResendVerificationEmail,
|
|
||||||
doUserCheckEmailVerified,
|
|
||||||
selectUser,
|
selectUser,
|
||||||
doFetchAccessToken,
|
|
||||||
selectAccessToken,
|
selectAccessToken,
|
||||||
|
makeSelectIsRewardClaimPending,
|
||||||
|
selectClaimedRewards,
|
||||||
|
rewards as REWARD_TYPES,
|
||||||
|
doClaimRewardType,
|
||||||
} from 'lbryinc';
|
} from 'lbryinc';
|
||||||
import UserSignUp from './view';
|
import { selectMyChannelClaims, selectBalance, selectFetchingMyChannels } from 'lbry-redux';
|
||||||
|
import UserSignIn from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
email: selectEmailToVerify(state),
|
email: selectEmailToVerify(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
accessToken: selectAccessToken(state),
|
accessToken: selectAccessToken(state),
|
||||||
|
channels: selectMyChannelClaims(state),
|
||||||
|
claimedRewards: selectClaimedRewards(state),
|
||||||
|
claimingReward: makeSelectIsRewardClaimPending()(state, {
|
||||||
|
reward_type: REWARD_TYPES.TYPE_CONFIRM_EMAIL,
|
||||||
|
}),
|
||||||
|
balance: selectBalance(state),
|
||||||
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
|
claimReward: () =>
|
||||||
checkEmailVerified: () => dispatch(doUserCheckEmailVerified()),
|
dispatch(
|
||||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
doClaimRewardType(REWARD_TYPES.TYPE_CONFIRM_EMAIL, {
|
||||||
|
notifyError: false,
|
||||||
|
})
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
select,
|
select,
|
||||||
perform
|
perform
|
||||||
)(UserSignUp);
|
)(UserSignIn);
|
||||||
|
|
|
@ -1,32 +1,101 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
import UserEmailNew from 'component/userEmailNew';
|
import UserEmailNew from 'component/userEmailNew';
|
||||||
import UserEmailVerify from 'component/userEmailVerify';
|
import UserEmailVerify from 'component/userEmailVerify';
|
||||||
|
import UserFirstChannel from 'component/userFirstChannel';
|
||||||
|
import UserVerify from 'component/userVerify';
|
||||||
|
import Spinner from 'component/spinner';
|
||||||
|
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
|
||||||
|
import { rewards as REWARDS } from 'lbryinc';
|
||||||
|
import usePrevious from 'util/use-previous';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: ?User,
|
user: ?User,
|
||||||
email: ?string,
|
email: ?string,
|
||||||
|
fetchingChannels: boolean,
|
||||||
|
channels: ?Array<string>,
|
||||||
|
balance: ?number,
|
||||||
|
fetchingChannels: boolean,
|
||||||
|
claimingReward: boolean,
|
||||||
|
claimReward: () => void,
|
||||||
|
claimedRewards: Array<Reward>,
|
||||||
|
history: { replace: string => void },
|
||||||
|
location: { search: string },
|
||||||
};
|
};
|
||||||
|
|
||||||
function UserSignUp(props: Props) {
|
function useFetched(fetching) {
|
||||||
const { email, user } = props;
|
const wasFetching = usePrevious(fetching);
|
||||||
const verifiedEmail = user && email && user.has_verified_email;
|
const [fetched, setFetched] = React.useState(false);
|
||||||
|
|
||||||
function getTitle() {
|
React.useEffect(() => {
|
||||||
if (!email) {
|
if (wasFetching && !fetching) {
|
||||||
return __('Get Rockin');
|
setFetched(true);
|
||||||
} else if (email && !verifiedEmail) {
|
|
||||||
return __('We Sent You An Email');
|
|
||||||
}
|
}
|
||||||
|
}, [wasFetching, fetching, setFetched]);
|
||||||
|
|
||||||
|
return fetched;
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserSignIn(props: Props) {
|
||||||
|
const { email, user, channels, claimingReward, claimReward, claimedRewards, balance, history, location } = props;
|
||||||
|
const { search } = location;
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const redirect = urlParams.get('redirect');
|
||||||
|
const hasFetchedReward = useFetched(claimingReward);
|
||||||
|
const hasVerifiedEmail = user && user.has_verified_email;
|
||||||
|
const rewardsApproved = user && user.is_reward_approved;
|
||||||
|
const channelCount = channels ? channels.length : 0;
|
||||||
|
const hasFetchedChannels = channels !== undefined;
|
||||||
|
const hasClaimedEmailAward = claimedRewards.some(reward => reward.reward_type === REWARDS.TYPE_CONFIRM_EMAIL);
|
||||||
|
const memoizedClaimReward = React.useCallback(() => {
|
||||||
|
claimReward();
|
||||||
|
}, [claimReward]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (hasVerifiedEmail && balance !== undefined && !hasClaimedEmailAward && !hasFetchedReward) {
|
||||||
|
memoizedClaimReward();
|
||||||
|
}
|
||||||
|
}, [hasVerifiedEmail, memoizedClaimReward, balance, hasClaimedEmailAward, hasFetchedReward]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!user ||
|
||||||
|
(balance === 0 && !hasFetchedReward) ||
|
||||||
|
(hasVerifiedEmail && balance === undefined) ||
|
||||||
|
(hasVerifiedEmail && !hasFetchedChannels)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div className="main--empty">
|
||||||
|
<Spinner delayed />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (balance === 0 && hasClaimedEmailAward) {
|
||||||
|
history.replace(redirect || '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewardsApproved && channelCount > 0) {
|
||||||
|
history.replace(redirect || '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewardsApproved && hasFetchedReward && balance && (balance === 0 || balance < DEFAULT_BID_FOR_FIRST_CHANNEL)) {
|
||||||
|
history.replace(redirect || '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h1 className="card__title--large">{getTitle()}</h1>
|
{hasVerifiedEmail && !rewardsApproved ? (
|
||||||
{!email && <UserEmailNew />}
|
<UserVerify />
|
||||||
{email && !verifiedEmail && <UserEmailVerify />}
|
) : (
|
||||||
|
<div className="main--contained">
|
||||||
|
{!email && !hasVerifiedEmail && <UserEmailNew />}
|
||||||
|
{email && !hasVerifiedEmail && <UserEmailVerify />}
|
||||||
|
{hasVerifiedEmail && balance && balance > 0 && channelCount === 0 && <UserFirstChannel />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserSignUp;
|
export default withRouter(UserSignIn);
|
||||||
|
|
14
src/ui/component/userSignOutButton/index.js
Normal file
14
src/ui/component/userSignOutButton/index.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doSignOut } from 'redux/actions/app';
|
||||||
|
import UserSignOutButton from './view';
|
||||||
|
|
||||||
|
const select = state => ({});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
signOut: () => dispatch(doSignOut()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(UserSignOutButton);
|
17
src/ui/component/userSignOutButton/view.jsx
Normal file
17
src/ui/component/userSignOutButton/view.jsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
button: string,
|
||||||
|
label?: string,
|
||||||
|
signOut: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function UserSignOutButton(props: Props) {
|
||||||
|
const { button = 'link', signOut, label } = props;
|
||||||
|
|
||||||
|
return <Button button={button} label={label || __('Sign Out')} onClick={signOut} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserSignOutButton;
|
|
@ -1,8 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as ICONS from 'constants/icons';
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import CardVerify from 'component/cardVerify';
|
import CardVerify from 'component/cardVerify';
|
||||||
import { Lbryio } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
errorMessage: ?string,
|
errorMessage: ?string,
|
||||||
|
@ -26,91 +28,87 @@ class UserVerify extends React.PureComponent<Props> {
|
||||||
const { errorMessage, isPending, verifyPhone } = this.props;
|
const { errorMessage, isPending, verifyPhone } = this.props;
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<section className="card card--section">
|
<section className="section--large">
|
||||||
<h1 className="card__title">{__('Final Human Proof')}</h1>
|
<h1 className="section__title--large">{__('Extra Verification Needed')}</h1>
|
||||||
<p className="card__subtitle">
|
<p>
|
||||||
To start the rewards approval process, please complete <strong>one and only one</strong> of the options
|
|
||||||
below. This is optional, and can be skipped at the bottom of the page.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card card--section">
|
|
||||||
<h2 className="card__title">{__('1) Proof via Phone')}</h2>
|
|
||||||
<p className="card__subtitle">
|
|
||||||
{`${__(
|
|
||||||
'You will receive an SMS text message confirming that your phone number is correct. Does not work for Canada and possibly other regions'
|
|
||||||
)}`}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="card__actions">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
verifyPhone();
|
|
||||||
}}
|
|
||||||
button="inverse"
|
|
||||||
label={__('Submit Phone Number')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="help">
|
|
||||||
{__('Standard messaging rates apply. LBRY will not text or call you otherwise. Having trouble?')}{' '}
|
|
||||||
<Button button="link" href="https://lbry.com/faq/phone" label={__('Read more.')} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card card--section">
|
|
||||||
<h2 className="card__title">{__('2) Proof via Credit')}</h2>
|
|
||||||
<p className="card__subtitle">
|
|
||||||
{`${__('If you have a valid credit or debit card, you can use it to instantly prove your humanity.')} ${__(
|
|
||||||
'LBRY does not store your credit card information. There is no charge at all for this, now or in the future.'
|
|
||||||
)} `}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="card__actions">
|
|
||||||
{errorMessage && <p className="error-text">{errorMessage}</p>}
|
|
||||||
<CardVerify
|
|
||||||
label={__('Perform Card Verification')}
|
|
||||||
disabled={isPending}
|
|
||||||
token={this.onToken}
|
|
||||||
stripeKey={Lbryio.getStripeToken()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="help">
|
|
||||||
{__('A $1 authorization may temporarily appear with your provider.')}{' '}
|
|
||||||
<Button
|
|
||||||
button="link"
|
|
||||||
href="https://lbry.com/faq/identity-requirements"
|
|
||||||
label={__('Read more about why we do this.')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card card--section">
|
|
||||||
<h2 className="card__title">{__('3) Proof via Chat')}</h2>
|
|
||||||
<p className="card__subtitle">
|
|
||||||
{__(
|
{__(
|
||||||
'A moderator capable of approving you is typically available in the discord server. Check out the #rewards-approval channel for more information.'
|
"We weren't able to auto-approve you for rewards. Please complete one of the steps below to unlock them."
|
||||||
)}{' '}
|
)}{' '}
|
||||||
{__(
|
<Button navigate="/" button="link" label={__('Skip')} />.
|
||||||
'This process will likely involve providing proof of a stable and established online or real-life identity.'
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="section">
|
||||||
|
<Card
|
||||||
|
icon={ICONS.PHONE}
|
||||||
|
title={__('Proof via Text')}
|
||||||
|
subtitle={__(
|
||||||
|
'You will receive an SMS text message confirming that your phone number is correct. Does not work for Canada and possibly other regions'
|
||||||
)}
|
)}
|
||||||
</p>
|
actions={
|
||||||
|
<Fragment>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
verifyPhone();
|
||||||
|
}}
|
||||||
|
button="primary"
|
||||||
|
label={__('Verify Phone Number')}
|
||||||
|
/>
|
||||||
|
<p className="help">
|
||||||
|
{__('Standard messaging rates apply. LBRY will not text or call you otherwise. Having trouble?')}{' '}
|
||||||
|
<Button button="link" href="https://lbry.com/faq/phone" label={__('Read more.')} />
|
||||||
|
</p>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="card__actions">
|
<div className="section__divider">
|
||||||
<Button href="https://chat.lbry.com" button="inverse" label={__('Join LBRY Chat')} />
|
<hr />
|
||||||
|
<p>{__('OR')}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card card--section">
|
<Card
|
||||||
<h2 className="card__title">{__('Or, Skip It Entirely')}</h2>
|
icon={ICONS.WALLET}
|
||||||
<p className="card__subtitle">
|
title={__('Proof via Credit')}
|
||||||
{__('You can continue without this step, but you will not be eligible to earn rewards.')}
|
subtitle={__(
|
||||||
</p>
|
'If you have a valid credit or debit card, you can use it to instantly prove your humanity. LBRY does not store your credit card information. There is no charge at all for this, now or in the future.'
|
||||||
|
)}
|
||||||
|
actions={
|
||||||
|
<Fragment>
|
||||||
|
{errorMessage && <p className="error-text">{errorMessage}</p>}
|
||||||
|
<CardVerify
|
||||||
|
label={__('Verify Card')}
|
||||||
|
disabled={isPending}
|
||||||
|
token={this.onToken}
|
||||||
|
stripeKey={Lbryio.getStripeToken()}
|
||||||
|
/>
|
||||||
|
<p className="help">
|
||||||
|
{__('A $1 authorization may temporarily appear with your provider.')}{' '}
|
||||||
|
<Button button="link" href="https://lbry.com/faq/identity-requirements" label={__('Read more')} />.
|
||||||
|
</p>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="card__actions">
|
<div className="section__divider">
|
||||||
<Button navigate="/" button="primary" label={__('Skip Rewards')} />
|
<hr />
|
||||||
|
<p>{__('OR')}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
<Card
|
||||||
|
icon={ICONS.CHAT}
|
||||||
|
title={__('Proof via Chat')}
|
||||||
|
subtitle={__(
|
||||||
|
'A moderator capable of approving you is typically available in the discord server. Check out the #rewards-approval channel for more information. This process will likely involve providing proof of a stable and established online or real-life identity.'
|
||||||
|
)}
|
||||||
|
actions={
|
||||||
|
<Fragment>
|
||||||
|
<Button href="https://chat.lbry.com" button="primary" label={__('Join LBRY Chat')} />
|
||||||
|
<p className="help">{__("We're friendly. We promise.")}</p>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import CopyableText from 'component/copyableText';
|
import CopyableText from 'component/copyableText';
|
||||||
import QRCode from 'component/common/qr-code';
|
import QRCode from 'component/common/qr-code';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
checkAddressIsMine: string => void,
|
checkAddressIsMine: string => void,
|
||||||
|
@ -46,29 +47,31 @@ class WalletAddress extends React.PureComponent<Props, State> {
|
||||||
const { showQR } = this.state;
|
const { showQR } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="card card--section">
|
<Card
|
||||||
<h2 className="card__title">{__('Receive Credits')}</h2>
|
title={__('Receive Credits')}
|
||||||
<p className="card__subtitle">
|
subtitle={__(
|
||||||
{__(
|
'Use this address to receive LBC. You can generate a new address at any time, and any previous addresses will continue to work.'
|
||||||
'Use this address to receive LBC. You can generate a new address at any time, and any previous addresses will continue to work.'
|
)}
|
||||||
)}
|
actions={
|
||||||
</p>
|
<React.Fragment>
|
||||||
<CopyableText label={__('Your Address')} copyable={receiveAddress} snackMessage={__('Address copied.')} />
|
<CopyableText label={__('Your Address')} copyable={receiveAddress} snackMessage={__('Address copied.')} />
|
||||||
|
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
{!IS_WEB && (
|
{!IS_WEB && (
|
||||||
<Button
|
<Button
|
||||||
button="inverse"
|
button="inverse"
|
||||||
label={__('Get New Address')}
|
label={__('Get New Address')}
|
||||||
onClick={getNewAddress}
|
onClick={getNewAddress}
|
||||||
disabled={gettingNewAddress}
|
disabled={gettingNewAddress}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Button button="link" label={showQR ? __('Hide QR code') : __('Show QR code')} onClick={this.toggleQR} />
|
<Button button="link" label={showQR ? __('Hide QR code') : __('Show QR code')} onClick={this.toggleQR} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showQR && <QRCode value={receiveAddress} paddingTop />}
|
{showQR && <QRCode value={receiveAddress} paddingTop />}
|
||||||
</section>
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import * as SETTINGS from 'constants/settings';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
doWalletStatus,
|
doWalletStatus,
|
||||||
|
@ -27,7 +26,6 @@ import {
|
||||||
selectUser,
|
selectUser,
|
||||||
} from 'lbryinc';
|
} from 'lbryinc';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
walletEncryptSucceeded: selectWalletEncryptSucceeded(state),
|
walletEncryptSucceeded: selectWalletEncryptSucceeded(state),
|
||||||
|
@ -36,7 +34,6 @@ const select = state => ({
|
||||||
walletEncrypted: selectWalletIsEncrypted(state),
|
walletEncrypted: selectWalletIsEncrypted(state),
|
||||||
walletHasTransactions: selectHasTransactions(state),
|
walletHasTransactions: selectHasTransactions(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
|
|
||||||
hasSyncedWallet: selectHasSyncedWallet(state),
|
hasSyncedWallet: selectHasSyncedWallet(state),
|
||||||
getSyncIsPending: selectGetSyncIsPending(state),
|
getSyncIsPending: selectGetSyncIsPending(state),
|
||||||
setSyncIsPending: selectSetSyncIsPending(state),
|
setSyncIsPending: selectSetSyncIsPending(state),
|
||||||
|
|
|
@ -1,404 +1,405 @@
|
||||||
// @flow
|
export default () => null;
|
||||||
import React, { useState, useEffect } from 'react';
|
// // @flow
|
||||||
import { Form, FormField, Submit } from 'component/common/form';
|
// import React, { useState } from 'react';
|
||||||
import Button from 'component/button';
|
// import { Form, FormField, Submit } from 'component/common/form';
|
||||||
import UserEmail from 'component/userEmail';
|
// import Button from 'component/button';
|
||||||
import * as ICONS from 'constants/icons';
|
// import UserEmail from 'component/userEmail';
|
||||||
|
// import * as ICONS from 'constants/icons';
|
||||||
|
|
||||||
import { getSavedPassword, setSavedPassword, deleteSavedPassword } from 'util/saved-passwords';
|
// import { getSavedPassword, setSavedPassword, deleteAuthToken } from 'util/saved-passwords';
|
||||||
|
|
||||||
type Props = {
|
// type Props = {
|
||||||
// wallet statuses
|
// // wallet statuses
|
||||||
walletEncryptSucceeded: boolean,
|
// walletEncryptSucceeded: boolean,
|
||||||
walletEncryptPending: boolean,
|
// walletEncryptPending: boolean,
|
||||||
walletDecryptSucceeded: boolean,
|
// walletDecryptSucceeded: boolean,
|
||||||
walletDecryptPending: boolean,
|
// walletDecryptPending: boolean,
|
||||||
updateWalletStatus: boolean,
|
// updateWalletStatus: boolean,
|
||||||
walletEncrypted: boolean,
|
// walletEncrypted: boolean,
|
||||||
// wallet methods
|
// // wallet methods
|
||||||
encryptWallet: (?string) => void,
|
// encryptWallet: (?string) => void,
|
||||||
decryptWallet: (?string) => void,
|
// decryptWallet: (?string) => void,
|
||||||
updateWalletStatus: () => void,
|
// updateWalletStatus: () => void,
|
||||||
// housekeeping
|
// // housekeeping
|
||||||
setPasswordSaved: () => void,
|
// setPasswordSaved: () => void,
|
||||||
syncEnabled: boolean,
|
// syncEnabled: boolean,
|
||||||
setClientSetting: (string, boolean | string) => void,
|
// setClientSetting: (string, boolean | string) => void,
|
||||||
isPasswordSaved: boolean,
|
// isPasswordSaved: boolean,
|
||||||
// data
|
// // data
|
||||||
user: any,
|
// user: any,
|
||||||
// sync statuses
|
// // sync statuses
|
||||||
hasSyncedWallet: boolean,
|
// hasSyncedWallet: boolean,
|
||||||
getSyncIsPending?: boolean,
|
// getSyncIsPending?: boolean,
|
||||||
syncApplyErrorMessage?: string,
|
// syncApplyErrorMessage?: string,
|
||||||
hashChanged: boolean,
|
// hashChanged: boolean,
|
||||||
// sync data
|
// // sync data
|
||||||
syncData: string | null,
|
// syncData: string | null,
|
||||||
syncHash: string | null,
|
// syncHash: string | null,
|
||||||
// sync methods
|
// // sync methods
|
||||||
syncApply: (string | null, string | null, string) => void,
|
// syncApply: (string | null, string | null, string) => void,
|
||||||
checkSync: () => void,
|
// checkSync: () => void,
|
||||||
setDefaultAccount: () => void,
|
// setDefaultAccount: () => void,
|
||||||
hasTransactions: boolean,
|
// hasTransactions: boolean,
|
||||||
};
|
// };
|
||||||
|
|
||||||
type State = {
|
// type State = {
|
||||||
newPassword: string,
|
// newPassword: string,
|
||||||
newPasswordConfirm: string,
|
// newPasswordConfirm: string,
|
||||||
passwordMatch: boolean,
|
// passwordMatch: boolean,
|
||||||
understandConfirmed: boolean,
|
// understandConfirmed: boolean,
|
||||||
understandError: boolean,
|
// understandError: boolean,
|
||||||
submitted: boolean,
|
// submitted: boolean,
|
||||||
failMessage: ?string,
|
// failMessage: ?string,
|
||||||
rememberPassword: boolean,
|
// rememberPassword: boolean,
|
||||||
showEmailReg: boolean,
|
// showEmailReg: boolean,
|
||||||
failed: boolean,
|
// failed: boolean,
|
||||||
enableSync: boolean,
|
// enableSync: boolean,
|
||||||
encryptWallet: boolean,
|
// encryptWallet: boolean,
|
||||||
obscurePassword: boolean,
|
// obscurePassword: boolean,
|
||||||
advancedMode: boolean,
|
// advancedMode: boolean,
|
||||||
showPasswordFields: boolean,
|
// showPasswordFields: boolean,
|
||||||
};
|
// };
|
||||||
|
|
||||||
function WalletSecurityAndSync(props: Props) {
|
// function WalletSecurityAndSync(props: Props) {
|
||||||
const {
|
// const {
|
||||||
// walletEncryptSucceeded,
|
// // walletEncryptSucceeded,
|
||||||
// walletEncryptPending,
|
// // walletEncryptPending,
|
||||||
// walletDecryptSucceeded,
|
// // walletDecryptSucceeded,
|
||||||
// walletDecryptPending,
|
// // walletDecryptPending,
|
||||||
// updateWalletStatus,
|
// // updateWalletStatus,
|
||||||
walletEncrypted,
|
// walletEncrypted,
|
||||||
encryptWallet,
|
// encryptWallet,
|
||||||
decryptWallet,
|
// decryptWallet,
|
||||||
syncEnabled,
|
// syncEnabled,
|
||||||
user,
|
// user,
|
||||||
hasSyncedWallet,
|
// hasSyncedWallet,
|
||||||
getSyncIsPending,
|
// getSyncIsPending,
|
||||||
syncApplyErrorMessage,
|
// syncApplyErrorMessage,
|
||||||
hashChanged,
|
// hashChanged,
|
||||||
syncData,
|
// syncData,
|
||||||
syncHash,
|
// syncHash,
|
||||||
syncApply,
|
// syncApply,
|
||||||
checkSync,
|
// checkSync,
|
||||||
hasTransactions,
|
// hasTransactions,
|
||||||
setDefaultAccount,
|
// // setDefaultAccount,
|
||||||
} = props;
|
// } = props;
|
||||||
|
|
||||||
const defaultComponentState: State = {
|
// const defaultComponentState: State = {
|
||||||
newPassword: '',
|
// newPassword: '',
|
||||||
newPasswordConfirm: '',
|
// newPasswordConfirm: '',
|
||||||
passwordMatch: false,
|
// passwordMatch: false,
|
||||||
understandConfirmed: false,
|
// understandConfirmed: false,
|
||||||
understandError: false,
|
// understandError: false,
|
||||||
submitted: false, // Prior actions could be marked complete
|
// submitted: false, // Prior actions could be marked complete
|
||||||
failMessage: undefined,
|
// failMessage: undefined,
|
||||||
rememberPassword: false,
|
// rememberPassword: false,
|
||||||
showEmailReg: false,
|
// showEmailReg: false,
|
||||||
failed: false,
|
// failed: false,
|
||||||
enableSync: syncEnabled,
|
// enableSync: syncEnabled,
|
||||||
encryptWallet: walletEncrypted,
|
// encryptWallet: walletEncrypted,
|
||||||
obscurePassword: true,
|
// obscurePassword: true,
|
||||||
advancedMode: false,
|
// advancedMode: false,
|
||||||
showPasswordFields: false,
|
// showPasswordFields: false,
|
||||||
};
|
// };
|
||||||
const [componentState, setComponentState] = useState<State>(defaultComponentState);
|
// const [componentState, setComponentState] = useState<State>(defaultComponentState);
|
||||||
|
|
||||||
const safeToSync = !hasTransactions || !hashChanged;
|
// const safeToSync = !hasTransactions || !hashChanged;
|
||||||
|
|
||||||
// on mount
|
// // on mount
|
||||||
useEffect(() => {
|
// // useEffect(() => {
|
||||||
checkSync();
|
// // checkSync();
|
||||||
getSavedPassword().then(p => {
|
// // getSavedPassword().then(p => {
|
||||||
if (p) {
|
// // if (p) {
|
||||||
setComponentState({
|
// // setComponentState({
|
||||||
...componentState,
|
// // ...componentState,
|
||||||
newPassword: p,
|
// // newPassword: p,
|
||||||
newPasswordConfirm: p,
|
// // newPasswordConfirm: p,
|
||||||
showPasswordFields: true,
|
// // showPasswordFields: true,
|
||||||
rememberPassword: true,
|
// // rememberPassword: true,
|
||||||
});
|
// // });
|
||||||
}
|
// // }
|
||||||
});
|
// // });
|
||||||
}, []);
|
// // }, []);
|
||||||
|
|
||||||
useEffect(() => {
|
// // useEffect(() => {
|
||||||
setComponentState({
|
// // setComponentState({
|
||||||
...componentState,
|
// // ...componentState,
|
||||||
passwordMatch: componentState.newPassword === componentState.newPasswordConfirm,
|
// // passwordMatch: componentState.newPassword === componentState.newPasswordConfirm,
|
||||||
});
|
// // });
|
||||||
}, [componentState.newPassword, componentState.newPasswordConfirm]);
|
// // }, [componentState.newPassword, componentState.newPasswordConfirm]);
|
||||||
|
|
||||||
const isEmailVerified = user && user.primary_email && user.has_verified_email;
|
// const isEmailVerified = user && user.primary_email && user.has_verified_email;
|
||||||
// const syncDisabledMessage = 'You cannot sync without an email';
|
// // const syncDisabledMessage = 'You cannot sync without an email';
|
||||||
|
|
||||||
function onChangeNewPassword(event: SyntheticInputEvent<>) {
|
// function onChangeNewPassword(event: SyntheticInputEvent<>) {
|
||||||
setComponentState({ ...componentState, newPassword: event.target.value || '' });
|
// setComponentState({ ...componentState, newPassword: event.target.value || '' });
|
||||||
}
|
// }
|
||||||
|
|
||||||
function onChangeRememberPassword(event: SyntheticInputEvent<>) {
|
// function onChangeRememberPassword(event: SyntheticInputEvent<>) {
|
||||||
if (componentState.rememberPassword) {
|
// if (componentState.rememberPassword) {
|
||||||
deleteSavedPassword();
|
// deleteAuthToken();
|
||||||
}
|
// }
|
||||||
setComponentState({ ...componentState, rememberPassword: event.target.checked });
|
// setComponentState({ ...componentState, rememberPassword: event.target.checked });
|
||||||
}
|
// }
|
||||||
|
|
||||||
function onChangeNewPasswordConfirm(event: SyntheticInputEvent<>) {
|
// function onChangeNewPasswordConfirm(event: SyntheticInputEvent<>) {
|
||||||
setComponentState({ ...componentState, newPasswordConfirm: event.target.value || '' });
|
// setComponentState({ ...componentState, newPasswordConfirm: event.target.value || '' });
|
||||||
}
|
// }
|
||||||
|
|
||||||
function onChangeUnderstandConfirm(event: SyntheticInputEvent<>) {
|
// function onChangeUnderstandConfirm(event: SyntheticInputEvent<>) {
|
||||||
setComponentState({ ...componentState, understandConfirmed: /^.?i understand.?$/i.test(event.target.value) });
|
// setComponentState({ ...componentState, understandConfirmed: /^.?i understand.?$/i.test(event.target.value) });
|
||||||
}
|
// }
|
||||||
|
|
||||||
function onChangeSync(event: SyntheticInputEvent<>) {
|
// function onChangeSync(event: SyntheticInputEvent<>) {
|
||||||
if (componentState.enableSync) {
|
// if (componentState.enableSync) {
|
||||||
setComponentState({ ...componentState, enableSync: false, newPassword: '', newPasswordConfirm: '' });
|
// setComponentState({ ...componentState, enableSync: false, newPassword: '', newPasswordConfirm: '' });
|
||||||
setComponentState({ ...componentState, enableSync: false, newPassword: '', newPasswordConfirm: '' });
|
// setComponentState({ ...componentState, enableSync: false, newPassword: '', newPasswordConfirm: '' });
|
||||||
}
|
// }
|
||||||
if (!(walletEncrypted || syncApplyErrorMessage || componentState.advancedMode)) {
|
// if (!(walletEncrypted || syncApplyErrorMessage || componentState.advancedMode)) {
|
||||||
easyApply();
|
// easyApply();
|
||||||
} else {
|
// } else {
|
||||||
setComponentState({ ...componentState, enableSync: true });
|
// setComponentState({ ...componentState, enableSync: true });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
function onChangeEncrypt(event: SyntheticInputEvent<>) {
|
// function onChangeEncrypt(event: SyntheticInputEvent<>) {
|
||||||
setComponentState({ ...componentState, encryptWallet: event.target.checked });
|
// setComponentState({ ...componentState, encryptWallet: event.target.checked });
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function easyApply() {
|
// async function easyApply() {
|
||||||
return new Promise((resolve, reject) => {
|
// return new Promise((resolve, reject) => {
|
||||||
return syncApply(syncHash, syncData, componentState.newPassword);
|
// return syncApply(syncHash, syncData, componentState.newPassword);
|
||||||
})
|
// })
|
||||||
.then(() => {
|
// .then(() => {
|
||||||
setComponentState({ ...componentState, enableSync: event.target.checked });
|
// setComponentState({ ...componentState, enableSync: event.target.checked });
|
||||||
})
|
// })
|
||||||
.catch();
|
// .catch();
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function apply() {
|
// async function apply() {
|
||||||
setComponentState({ ...componentState, failed: false });
|
// setComponentState({ ...componentState, failed: false });
|
||||||
|
|
||||||
await checkSync();
|
// await checkSync();
|
||||||
|
|
||||||
if (componentState.enableSync) {
|
// if (componentState.enableSync) {
|
||||||
await syncApply(syncHash, syncData, componentState.newPassword);
|
// await syncApply(syncHash, syncData, componentState.newPassword);
|
||||||
if (syncApplyErrorMessage) {
|
// if (syncApplyErrorMessage) {
|
||||||
setComponentState({ ...componentState, failed: true });
|
// setComponentState({ ...componentState, failed: true });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (walletEncrypted) {
|
// if (walletEncrypted) {
|
||||||
await decryptWallet();
|
// await decryptWallet();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (componentState.encryptWallet && !componentState.failed) {
|
// if (componentState.encryptWallet && !componentState.failed) {
|
||||||
await encryptWallet(componentState.newPassword)
|
// await encryptWallet(componentState.newPassword)
|
||||||
.then(() => {})
|
// .then(() => {})
|
||||||
.catch(() => {
|
// .catch(() => {
|
||||||
setComponentState({ ...componentState, failed: false });
|
// setComponentState({ ...componentState, failed: false });
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (componentState.rememberPassword && !componentState.failed) {
|
// if (componentState.rememberPassword && !componentState.failed) {
|
||||||
setSavedPassword(componentState.newPassword);
|
// setSavedPassword(componentState.newPassword);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<React.Fragment>
|
// <React.Fragment>
|
||||||
<section className="card card--section">
|
// <section className="card card--section">
|
||||||
<h2 className="card__title">{__('Wallet Sync and Security')}</h2>
|
// <h2 className="card__title">{__('Wallet Sync and Security')}</h2>
|
||||||
{!isEmailVerified && (
|
// {!isEmailVerified && (
|
||||||
<React.Fragment>
|
// <React.Fragment>
|
||||||
<p className="card__subtitle">
|
// <p className="card__subtitle">
|
||||||
{__(`It looks like we don't have your email.`)}{' '}
|
// {__(`It looks like we don't have your email.`)}{' '}
|
||||||
<Button
|
// <Button
|
||||||
button="link"
|
// button="link"
|
||||||
label={__('Verify your email')}
|
// label={__('Verify your email')}
|
||||||
onClick={() => setComponentState({ ...componentState, showEmailReg: !componentState.showEmailReg })}
|
// onClick={() => setComponentState({ ...componentState, showEmailReg: !componentState.showEmailReg })}
|
||||||
/>{' '}
|
// />{' '}
|
||||||
{__(`and then come back here.`)}
|
// {__(`and then come back here.`)}
|
||||||
</p>
|
// </p>
|
||||||
{componentState.showEmailReg && <UserEmail />}
|
// {componentState.showEmailReg && <UserEmail />}
|
||||||
</React.Fragment>
|
// </React.Fragment>
|
||||||
)}
|
// )}
|
||||||
<p>
|
// <p>
|
||||||
{__(
|
// {__(
|
||||||
'Your LBRY password can help you encrypt your wallet or sync it to another device. You must use the same LBRY password on every device if you wish to sync.'
|
// 'Your LBRY password can help you encrypt your wallet or sync it to another device. You must use the same LBRY password on every device if you wish to sync.'
|
||||||
)}{' '}
|
// )}{' '}
|
||||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />.
|
// <Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />.
|
||||||
</p>
|
// </p>
|
||||||
{/* Errors and status */}
|
// {/* Errors and status */}
|
||||||
{!componentState.advancedMode && (
|
// {!componentState.advancedMode && (
|
||||||
<React.Fragment>
|
// <React.Fragment>
|
||||||
<p className="card__subtitle">
|
// <p className="card__subtitle">
|
||||||
{__(`Easy Mode: Sync and go with default security! Don't trust your roommate?`)}{' '}
|
// {__(`Easy Mode: Sync and go with default security! Don't trust your roommate?`)}{' '}
|
||||||
<Button
|
// <Button
|
||||||
button="link"
|
// button="link"
|
||||||
label={__('Advanced Mode')}
|
// label={__('Advanced Mode')}
|
||||||
onClick={() => setComponentState({ ...componentState, advancedMode: !componentState.advancedMode })}
|
// onClick={() => setComponentState({ ...componentState, advancedMode: !componentState.advancedMode })}
|
||||||
/>
|
// />
|
||||||
.
|
// .
|
||||||
</p>
|
// </p>
|
||||||
</React.Fragment>
|
// </React.Fragment>
|
||||||
)}
|
// )}
|
||||||
{componentState.advancedMode && (
|
// {componentState.advancedMode && (
|
||||||
<React.Fragment>
|
// <React.Fragment>
|
||||||
<p className="card__subtitle">
|
// <p className="card__subtitle">
|
||||||
{__('Advanced Mode: Enter a password that matches your other devices LBRY password.')}{' '}
|
// {__('Advanced Mode: Enter a password that matches your other devices LBRY password.')}{' '}
|
||||||
<Button
|
// <Button
|
||||||
button="link"
|
// button="link"
|
||||||
label={__('Easy Mode')}
|
// label={__('Easy Mode')}
|
||||||
onClick={() => setComponentState({ ...componentState, advancedMode: !componentState.advancedMode })}
|
// onClick={() => setComponentState({ ...componentState, advancedMode: !componentState.advancedMode })}
|
||||||
/>
|
// />
|
||||||
.
|
// .
|
||||||
</p>
|
// </p>
|
||||||
</React.Fragment>
|
// </React.Fragment>
|
||||||
)}
|
// )}
|
||||||
{syncApplyErrorMessage && <div className="card__subtitle--status">{__(syncApplyErrorMessage)}</div>}
|
// {syncApplyErrorMessage && <div className="card__subtitle--status">{__(syncApplyErrorMessage)}</div>}
|
||||||
|
|
||||||
<Form onSubmit={() => apply()}>
|
// <Form onSubmit={() => apply()}>
|
||||||
{componentState.advancedMode && (
|
// {componentState.advancedMode && (
|
||||||
<FormField
|
// <FormField
|
||||||
type="checkbox"
|
// type="checkbox"
|
||||||
name="sync_enabled"
|
// name="sync_enabled"
|
||||||
checked={componentState.enableSync}
|
// checked={componentState.enableSync}
|
||||||
disabled={!isEmailVerified || !safeToSync}
|
// disabled={!isEmailVerified || !safeToSync}
|
||||||
prefix={<span className="badge badge--alert">ALPHA</span>}
|
// prefix={<span className="badge badge--alert">ALPHA</span>}
|
||||||
onChange={event => onChangeSync(event)}
|
// onChange={event => onChangeSync(event)}
|
||||||
label={
|
// label={
|
||||||
<React.Fragment>
|
// <React.Fragment>
|
||||||
{__('Enable Sync')} <Button button="link" label={__('(?)')} href="https://lbry.com/privacypolicy" />{' '}
|
// {__('Enable Sync')} <Button button="link" label={__('(?)')} href="https://lbry.com/privacypolicy" />{' '}
|
||||||
<span className="badge badge--alert">ALPHA</span>
|
// <span className="badge badge--alert">ALPHA</span>
|
||||||
</React.Fragment>
|
// </React.Fragment>
|
||||||
}
|
// }
|
||||||
/>
|
// />
|
||||||
)}
|
// )}
|
||||||
{!componentState.advancedMode && (
|
// {!componentState.advancedMode && (
|
||||||
<Button
|
// <Button
|
||||||
button="primary"
|
// button="primary"
|
||||||
label={__('Sync my wallet')}
|
// label={__('Sync my wallet')}
|
||||||
onClick={() => syncApply(syncHash, syncData, componentState.newPassword)}
|
// onClick={() => syncApply(syncHash, syncData, componentState.newPassword)}
|
||||||
/>
|
// />
|
||||||
)}
|
// )}
|
||||||
{(walletEncrypted ||
|
// {(walletEncrypted ||
|
||||||
syncApplyErrorMessage ||
|
// syncApplyErrorMessage ||
|
||||||
componentState.advancedMode ||
|
// componentState.advancedMode ||
|
||||||
componentState.showPasswordFields) && (
|
// componentState.showPasswordFields) && (
|
||||||
<React.Fragment>
|
// <React.Fragment>
|
||||||
<FormField
|
// <FormField
|
||||||
autoFocus
|
// autoFocus
|
||||||
inputButton={
|
// inputButton={
|
||||||
<Button
|
// <Button
|
||||||
icon={componentState.obscurePassword ? ICONS.EYE : ICONS.EYE_OFF}
|
// icon={componentState.obscurePassword ? ICONS.EYE : ICONS.EYE_OFF}
|
||||||
button={'primary'}
|
// button={'primary'}
|
||||||
onClick={() =>
|
// onClick={() =>
|
||||||
setComponentState({ ...componentState, obscurePassword: !componentState.obscurePassword })
|
// setComponentState({ ...componentState, obscurePassword: !componentState.obscurePassword })
|
||||||
}
|
// }
|
||||||
/>
|
// />
|
||||||
}
|
// }
|
||||||
label={__('Password')}
|
// label={__('Password')}
|
||||||
placeholder={__('Shh...')}
|
// placeholder={__('Shh...')}
|
||||||
type={componentState.obscurePassword ? 'password' : 'text'}
|
// type={componentState.obscurePassword ? 'password' : 'text'}
|
||||||
name="wallet-new-password"
|
// name="wallet-new-password"
|
||||||
onChange={event => onChangeNewPassword(event)}
|
// onChange={event => onChangeNewPassword(event)}
|
||||||
value={componentState.newPassword}
|
// value={componentState.newPassword}
|
||||||
/>
|
// />
|
||||||
<FormField
|
// <FormField
|
||||||
error={componentState.passwordMatch === false ? 'No match' : false}
|
// error={componentState.passwordMatch === false ? 'No match' : false}
|
||||||
label={__('Same Password')}
|
// label={__('Same Password')}
|
||||||
placeholder={__('Your eyes only')}
|
// placeholder={__('Your eyes only')}
|
||||||
type="password"
|
// type="password"
|
||||||
name="wallet-new-password-confirm"
|
// name="wallet-new-password-confirm"
|
||||||
onChange={event => onChangeNewPasswordConfirm(event)}
|
// onChange={event => onChangeNewPasswordConfirm(event)}
|
||||||
value={componentState.newPasswordConfirm}
|
// value={componentState.newPasswordConfirm}
|
||||||
/>
|
// />
|
||||||
<FormField
|
// <FormField
|
||||||
label={__('Remember Password')}
|
// label={__('Remember Password')}
|
||||||
type="checkbox"
|
// type="checkbox"
|
||||||
name="wallet-remember-password"
|
// name="wallet-remember-password"
|
||||||
onChange={event => onChangeRememberPassword(event)}
|
// onChange={event => onChangeRememberPassword(event)}
|
||||||
checked={componentState.rememberPassword}
|
// checked={componentState.rememberPassword}
|
||||||
/>
|
// />
|
||||||
</React.Fragment>
|
// </React.Fragment>
|
||||||
)}
|
// )}
|
||||||
|
|
||||||
{/* Confirmation */}
|
// {/* Confirmation */}
|
||||||
|
|
||||||
{(walletEncrypted || componentState.advancedMode) && (
|
// {(walletEncrypted || componentState.advancedMode) && (
|
||||||
<React.Fragment>
|
// <React.Fragment>
|
||||||
<FormField
|
// <FormField
|
||||||
type="checkbox"
|
// type="checkbox"
|
||||||
name="encrypt_enabled"
|
// name="encrypt_enabled"
|
||||||
checked={componentState.encryptWallet}
|
// checked={componentState.encryptWallet}
|
||||||
disabled={false}
|
// disabled={false}
|
||||||
onChange={event => onChangeEncrypt(event)}
|
// onChange={event => onChangeEncrypt(event)}
|
||||||
label={__('Encrypt Wallet')}
|
// label={__('Encrypt Wallet')}
|
||||||
/>
|
// />
|
||||||
<div className="card__subtitle--status">
|
// <div className="card__subtitle--status">
|
||||||
{__(
|
// {__(
|
||||||
'If your password is lost, it cannot be recovered. You will not be able to access your wallet without a password.'
|
// 'If your password is lost, it cannot be recovered. You will not be able to access your wallet without a password.'
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
<FormField
|
// <FormField
|
||||||
error={componentState.understandError === true ? 'You must enter "I understand"' : false}
|
// error={componentState.understandError === true ? 'You must enter "I understand"' : false}
|
||||||
label={__('Enter "I understand"')}
|
// label={__('Enter "I understand"')}
|
||||||
placeholder={__('I understand')}
|
// placeholder={__('I understand')}
|
||||||
type="text"
|
// type="text"
|
||||||
name="wallet-understand"
|
// name="wallet-understand"
|
||||||
onChange={event => onChangeUnderstandConfirm(event)}
|
// onChange={event => onChangeUnderstandConfirm(event)}
|
||||||
/>
|
// />
|
||||||
</React.Fragment>
|
// </React.Fragment>
|
||||||
)}
|
// )}
|
||||||
|
|
||||||
{componentState.failMessage && <div className="error-text">{__(componentState.failMessage)}</div>}
|
// {componentState.failMessage && <div className="error-text">{__(componentState.failMessage)}</div>}
|
||||||
{(walletEncrypted || componentState.advancedMode || syncApplyErrorMessage) && (
|
// {(walletEncrypted || componentState.advancedMode || syncApplyErrorMessage) && (
|
||||||
<Submit
|
// <Submit
|
||||||
disabled={!componentState.passwordMatch || (!componentState.enableSync && !componentState.encryptWallet)}
|
// disabled={!componentState.passwordMatch || (!componentState.enableSync && !componentState.encryptWallet)}
|
||||||
label={componentState.failMessage ? __('Encrypting Wallet') : __('Apply')}
|
// label={componentState.failMessage ? __('Encrypting Wallet') : __('Apply')}
|
||||||
/>
|
// />
|
||||||
)}
|
// )}
|
||||||
</Form>
|
// </Form>
|
||||||
</section>
|
// </section>
|
||||||
|
|
||||||
{/* Testing stuff and Diagnostics */}
|
// {/* Testing stuff and Diagnostics */}
|
||||||
|
|
||||||
<section className="card card--section">
|
// <section className="card card--section">
|
||||||
<Button
|
// <Button
|
||||||
button="primary"
|
// button="primary"
|
||||||
label={__('Sync Apply')}
|
// label={__('Sync Apply')}
|
||||||
onClick={() => syncApply(syncHash, syncData, componentState.newPassword)}
|
// onClick={() => syncApply(syncHash, syncData, componentState.newPassword)}
|
||||||
/>{' '}
|
// />{' '}
|
||||||
<Button button="primary" label={__('Check Sync')} onClick={() => checkSync()} />{' '}
|
// <Button button="primary" label={__('Check Sync')} onClick={() => checkSync()} />{' '}
|
||||||
<Button button="primary" label={__('Setpass test')} onClick={() => setSavedPassword('testpass')} />{' '}
|
// <Button button="primary" label={__('Setpass test')} onClick={() => setSavedPassword('testpass')} />{' '}
|
||||||
<Button
|
// <Button
|
||||||
button="primary"
|
// button="primary"
|
||||||
label={__('Getpass test')}
|
// label={__('Getpass test')}
|
||||||
onClick={() => getSavedPassword().then(p => setComponentState({ ...componentState, newPassword: p }))}
|
// onClick={() => getSavedPassword().then(p => setComponentState({ ...componentState, newPassword: p }))}
|
||||||
/>{' '}
|
// />{' '}
|
||||||
<Button button="primary" label={__('Deletepass test')} onClick={() => deleteSavedPassword()} />{' '}
|
// <Button button="primary" label={__('Deletepass test')} onClick={() => deleteAuthToken()} />{' '}
|
||||||
<p>
|
// <p>
|
||||||
password:{' '}
|
// password:{' '}
|
||||||
{componentState.newPassword
|
// {componentState.newPassword
|
||||||
? componentState.newPassword
|
// ? componentState.newPassword
|
||||||
: componentState.newPassword === ''
|
// : componentState.newPassword === ''
|
||||||
? 'blankString'
|
// ? 'blankString'
|
||||||
: 'null'}
|
// : 'null'}
|
||||||
</p>
|
// </p>
|
||||||
<p>encryptWallet: {String(componentState.encryptWallet)}</p>
|
// <p>encryptWallet: {String(componentState.encryptWallet)}</p>
|
||||||
<p>enableSync: {String(componentState.enableSync)}</p>
|
// <p>enableSync: {String(componentState.enableSync)}</p>
|
||||||
<p>syncApplyError: {String(syncApplyErrorMessage)}</p>
|
// <p>syncApplyError: {String(syncApplyErrorMessage)}</p>
|
||||||
<p>Has Synced: {String(hasSyncedWallet)}</p>
|
// <p>Has Synced: {String(hasSyncedWallet)}</p>
|
||||||
<p>getSyncPending: {String(getSyncIsPending)}</p>
|
// <p>getSyncPending: {String(getSyncIsPending)}</p>
|
||||||
<p>syncEnabled: {String(syncEnabled)}</p>
|
// <p>syncEnabled: {String(syncEnabled)}</p>
|
||||||
<p>syncHash: {syncHash ? syncHash.slice(0, 10) : 'null'}</p>
|
// <p>syncHash: {syncHash ? syncHash.slice(0, 10) : 'null'}</p>
|
||||||
<p>syncData: {syncData ? syncData.slice(0, 10) : 'null'}</p>
|
// <p>syncData: {syncData ? syncData.slice(0, 10) : 'null'}</p>
|
||||||
<p>walletEncrypted: {String(walletEncrypted)}</p>
|
// <p>walletEncrypted: {String(walletEncrypted)}</p>
|
||||||
<p>emailRegistered: {String(isEmailVerified)}</p>
|
// <p>emailRegistered: {String(isEmailVerified)}</p>
|
||||||
<p>hashChanged: {String(hashChanged)}</p>
|
// <p>hashChanged: {String(hashChanged)}</p>
|
||||||
</section>
|
// </section>
|
||||||
</React.Fragment>
|
// </React.Fragment>
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
export default WalletSecurityAndSync;
|
// export default WalletSecurityAndSync;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Button from 'component/button';
|
||||||
import { Form, FormField } from 'component/common/form';
|
import { Form, FormField } from 'component/common/form';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { validateSendTx } from 'util/form-validation';
|
import { validateSendTx } from 'util/form-validation';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
|
||||||
type DraftTransaction = {
|
type DraftTransaction = {
|
||||||
address: string,
|
address: string,
|
||||||
|
@ -36,71 +37,73 @@ class WalletSend extends React.PureComponent<Props> {
|
||||||
const { balance } = this.props;
|
const { balance } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="card card--section">
|
<Card
|
||||||
<h2 className="card__title">{__('Send Credits')}</h2>
|
title={__('Send Credits')}
|
||||||
<p className="card__subtitle">{__('Send LBC to your friends or favorite creators.')}</p>
|
subtitle={__('Send LBC to your friends or favorite creators.')}
|
||||||
|
actions={
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
address: '',
|
||||||
|
amount: '',
|
||||||
|
}}
|
||||||
|
onSubmit={this.handleSubmit}
|
||||||
|
validate={validateSendTx}
|
||||||
|
render={({ values, errors, touched, handleChange, handleBlur, handleSubmit }) => (
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<fieldset-group class="fieldset-group--smushed">
|
||||||
|
<FormField
|
||||||
|
type="number"
|
||||||
|
name="amount"
|
||||||
|
label={__('Amount')}
|
||||||
|
postfix={__('LBC')}
|
||||||
|
className="form-field--price-amount"
|
||||||
|
affixClass="form-field--fix-no-height"
|
||||||
|
min="0"
|
||||||
|
step="any"
|
||||||
|
placeholder="12.34"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.amount}
|
||||||
|
/>
|
||||||
|
|
||||||
<Formik
|
<FormField
|
||||||
initialValues={{
|
type="text"
|
||||||
address: '',
|
name="address"
|
||||||
amount: '',
|
placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs"
|
||||||
}}
|
className="form-field--address"
|
||||||
onSubmit={this.handleSubmit}
|
label={__('Recipient address')}
|
||||||
validate={validateSendTx}
|
onChange={handleChange}
|
||||||
render={({ values, errors, touched, handleChange, handleBlur, handleSubmit }) => (
|
onBlur={handleBlur}
|
||||||
<Form onSubmit={handleSubmit}>
|
value={values.address}
|
||||||
<fieldset-group class="fieldset-group--smushed">
|
/>
|
||||||
<FormField
|
</fieldset-group>
|
||||||
type="number"
|
<div className="card__actions">
|
||||||
name="amount"
|
<Button
|
||||||
label={__('Amount')}
|
button="primary"
|
||||||
postfix={__('LBC')}
|
type="submit"
|
||||||
className="form-field--price-amount"
|
label={__('Send')}
|
||||||
affixClass="form-field--fix-no-height"
|
disabled={
|
||||||
min="0"
|
!values.address ||
|
||||||
step="any"
|
!!Object.keys(errors).length ||
|
||||||
placeholder="12.34"
|
!(parseFloat(values.amount) > 0.0) ||
|
||||||
onChange={handleChange}
|
parseFloat(values.amount) === balance
|
||||||
onBlur={handleBlur}
|
}
|
||||||
value={values.amount}
|
/>
|
||||||
/>
|
{!!Object.keys(errors).length || (
|
||||||
|
<span className="error-text">
|
||||||
<FormField
|
{(!!values.address && touched.address && errors.address) ||
|
||||||
type="text"
|
(!!values.amount && touched.amount && errors.amount) ||
|
||||||
name="address"
|
(parseFloat(values.amount) === balance &&
|
||||||
placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs"
|
__('Decrease amount to account for transaction fee')) ||
|
||||||
className="form-field--address"
|
(parseFloat(values.amount) > balance && __('Not enough credits'))}
|
||||||
label={__('Recipient address')}
|
</span>
|
||||||
onChange={handleChange}
|
)}
|
||||||
onBlur={handleBlur}
|
</div>
|
||||||
value={values.address}
|
</Form>
|
||||||
/>
|
)}
|
||||||
</fieldset-group>
|
/>
|
||||||
<div className="card__actions">
|
}
|
||||||
<Button
|
/>
|
||||||
button="primary"
|
|
||||||
type="submit"
|
|
||||||
label={__('Send')}
|
|
||||||
disabled={
|
|
||||||
!values.address ||
|
|
||||||
!!Object.keys(errors).length ||
|
|
||||||
!(parseFloat(values.amount) > 0.0) ||
|
|
||||||
parseFloat(values.amount) === balance
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{!!Object.keys(errors).length || (
|
|
||||||
<span className="error-text">
|
|
||||||
{(!!values.address && touched.address && errors.address) ||
|
|
||||||
(!!values.amount && touched.amount && errors.amount) ||
|
|
||||||
(parseFloat(values.amount) === balance && __('Decrease amount to account for transaction fee')) ||
|
|
||||||
(parseFloat(values.amount) > balance && __('Not enough credits'))}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,20 +158,9 @@ export const CLAIM_REWARD_FAILURE = 'CLAIM_REWARD_FAILURE';
|
||||||
export const CLAIM_REWARD_CLEAR_ERROR = 'CLAIM_REWARD_CLEAR_ERROR';
|
export const CLAIM_REWARD_CLEAR_ERROR = 'CLAIM_REWARD_CLEAR_ERROR';
|
||||||
export const FETCH_REWARD_CONTENT_COMPLETED = 'FETCH_REWARD_CONTENT_COMPLETED';
|
export const FETCH_REWARD_CONTENT_COMPLETED = 'FETCH_REWARD_CONTENT_COMPLETED';
|
||||||
|
|
||||||
// ShapeShift
|
// Language
|
||||||
export const GET_SUPPORTED_COINS_START = 'GET_SUPPORTED_COINS_START';
|
export const DOWNLOAD_LANGUAGE_SUCCEEDED = 'DOWNLOAD_LANGUAGE_SUCCEEDED';
|
||||||
export const GET_SUPPORTED_COINS_SUCCESS = 'GET_SUPPORTED_COINS_SUCCESS';
|
export const DOWNLOAD_LANGUAGE_FAILED = 'DOWNLOAD_LANGUAGE_FAILED';
|
||||||
export const GET_SUPPORTED_COINS_FAIL = 'GET_SUPPORTED_COINS_FAIL';
|
|
||||||
export const GET_COIN_STATS_START = 'GET_COIN_STATS_START';
|
|
||||||
export const GET_COIN_STATS_SUCCESS = 'GET_COIN_STATS_SUCCESS';
|
|
||||||
export const GET_COIN_STATS_FAIL = 'GET_COIN_STATS_FAIL';
|
|
||||||
export const PREPARE_SHAPE_SHIFT_START = 'PREPARE_SHAPE_SHIFT_START';
|
|
||||||
export const PREPARE_SHAPE_SHIFT_SUCCESS = 'PREPARE_SHAPE_SHIFT_SUCCESS';
|
|
||||||
export const PREPARE_SHAPE_SHIFT_FAIL = 'PREPARE_SHAPE_SHIFT_FAIL';
|
|
||||||
export const GET_ACTIVE_SHIFT_START = 'GET_ACTIVE_SHIFT_START';
|
|
||||||
export const GET_ACTIVE_SHIFT_SUCCESS = 'GET_ACTIVE_SHIFT_SUCCESS';
|
|
||||||
export const GET_ACTIVE_SHIFT_FAIL = 'GET_ACTIVE_SHIFT_FAIL';
|
|
||||||
export const CLEAR_SHAPE_SHIFT = 'CLEAR_SHAPE_SHIFT';
|
|
||||||
|
|
||||||
// Subscriptions
|
// Subscriptions
|
||||||
export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
|
export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const AUTH = 'auth';
|
export const AUTH = 'signin';
|
||||||
export const BACKUP = 'backup';
|
export const BACKUP = 'backup';
|
||||||
export const CHANNEL = 'channel';
|
export const CHANNEL = 'channel';
|
||||||
export const DISCOVER = 'discover';
|
export const DISCOVER = 'discover';
|
||||||
|
|
|
@ -36,7 +36,6 @@ import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import 'scss/all.scss';
|
import 'scss/all.scss';
|
||||||
|
|
||||||
const APPPAGEURL = 'lbry://?';
|
const APPPAGEURL = 'lbry://?';
|
||||||
const COOKIE_EXPIRE_TIME = 60 * 60 * 24 * 365; // 1 year
|
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
const { autoUpdater } = remote.require('electron-updater');
|
const { autoUpdater } = remote.require('electron-updater');
|
||||||
autoUpdater.logger = remote.require('electron-log');
|
autoUpdater.logger = remote.require('electron-log');
|
||||||
|
@ -78,18 +77,19 @@ Lbryio.setOverride(
|
||||||
throw new Error(__('auth_token is missing from response'));
|
throw new Error(__('auth_token is missing from response'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const newAuthToken = response.auth_token;
|
authToken = response.auth_token;
|
||||||
authToken = newAuthToken;
|
|
||||||
|
|
||||||
// @if TARGET='web'
|
let date = new Date();
|
||||||
cookie.serialize('auth_token', authToken, {
|
date.setFullYear(date.getFullYear() + 1);
|
||||||
maxAge: COOKIE_EXPIRE_TIME,
|
document.cookie = cookie.serialize('auth_token', authToken, {
|
||||||
|
expires: date,
|
||||||
});
|
});
|
||||||
// @endif
|
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
ipcRenderer.send('set-auth-token', authToken);
|
ipcRenderer.send('set-auth-token', authToken);
|
||||||
// @endif
|
// @endif
|
||||||
resolve();
|
|
||||||
|
resolve(authToken);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,19 +12,24 @@ const ModalFirstSubscription = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal type="custom" isOpen contentLabel="Subscriptions 101" title={__('Subscriptions 101')}>
|
<Modal type="custom" isOpen contentLabel="Subscriptions 101" title={__('Subscriptions 101')}>
|
||||||
<p>{__('You just subscribed to your first channel. Awesome!')}</p>
|
<div className="section__subtitle">
|
||||||
<p>{__('A few quick things to know:')}</p>
|
<p>{__('You just subscribed to your first channel. Awesome!')}</p>
|
||||||
<p>
|
<p>{__('A few quick things to know:')}</p>
|
||||||
{__(
|
</div>
|
||||||
'1) This app will automatically download new free content from channels you are subscribed to. You may configure this in Settings or on the Subscriptions page.'
|
<ol className="section">
|
||||||
)}
|
<li>
|
||||||
</p>
|
{__(
|
||||||
<p>
|
'This app will automatically download new free content from channels you are subscribed to. You may configure this in Settings or on the Subscriptions page.'
|
||||||
{__(
|
)}
|
||||||
'2) If we have your email address, we will send you notifications related to new content. You may configure these emails from the Help page.'
|
{__('(Only available on the desktop app.)')}
|
||||||
)}
|
</li>
|
||||||
</p>
|
<li>
|
||||||
<div className="modal__buttons">
|
{__(
|
||||||
|
'If we have your email address, we will send you notifications related to new content. You may configure these emails from the Help page.'
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<div className="section__actions">
|
||||||
<Button button="primary" onClick={closeModal} label={__('Got it')} />
|
<Button button="primary" onClick={closeModal} label={__('Got it')} />
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Modal } from 'modal/modal';
|
import { Modal } from 'modal/modal';
|
||||||
import { deleteSavedPassword } from 'util/saved-passwords';
|
import { deleteAuthToken } from 'util/saved-passwords';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
closeModal: () => void,
|
closeModal: () => void,
|
||||||
|
@ -18,7 +18,7 @@ class ModalPasswordUnsave extends React.PureComponent<Props> {
|
||||||
confirmButtonLabel={__('Forget')}
|
confirmButtonLabel={__('Forget')}
|
||||||
abortButtonLabel={__('Nevermind')}
|
abortButtonLabel={__('Nevermind')}
|
||||||
onConfirmed={() =>
|
onConfirmed={() =>
|
||||||
deleteSavedPassword().then(() => {
|
deleteAuthToken().then(() => {
|
||||||
this.props.closeModal();
|
this.props.closeModal();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ import UserPhoneVerify from 'component/userPhoneVerify';
|
||||||
import { Redirect } from 'react-router';
|
import { Redirect } from 'react-router';
|
||||||
|
|
||||||
const LazyUserPhoneNew = React.lazy(() =>
|
const LazyUserPhoneNew = React.lazy(() =>
|
||||||
import(/* webpackChunkName: "userPhoneNew" */
|
import(
|
||||||
'component/userPhoneNew')
|
/* webpackChunkName: "userPhoneNew" */
|
||||||
|
'component/userPhoneNew'
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
|
@ -11,7 +12,7 @@ const perform = (dispatch, ownProps) => ({
|
||||||
} = ownProps;
|
} = ownProps;
|
||||||
const currentPath = pathname.split('/$/')[1];
|
const currentPath = pathname.split('/$/')[1];
|
||||||
dispatch(doHideModal());
|
dispatch(doHideModal());
|
||||||
history.push(`/$/auth?redirect=${currentPath}`);
|
history.push(`/$/${PAGES.AUTH}?redirect=${currentPath}`);
|
||||||
},
|
},
|
||||||
closeModal: () => dispatch(doHideModal()),
|
closeModal: () => dispatch(doHideModal()),
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Modal } from 'modal/modal';
|
import { Modal } from 'modal/modal';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { deleteSavedPassword } from 'util/saved-passwords';
|
import { deleteAuthToken } from 'util/saved-passwords';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
closeModal: () => void,
|
closeModal: () => void,
|
||||||
|
@ -24,7 +24,7 @@ class ModalWalletDecrypt extends React.PureComponent<Props, State> {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
|
|
||||||
if (state.submitted && props.walletDecryptSucceded === true) {
|
if (state.submitted && props.walletDecryptSucceded === true) {
|
||||||
deleteSavedPassword();
|
deleteAuthToken();
|
||||||
props.closeModal();
|
props.closeModal();
|
||||||
props.updateWalletStatus();
|
props.updateWalletStatus();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,27 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
|
||||||
import RewardSummary from 'component/rewardSummary';
|
import RewardSummary from 'component/rewardSummary';
|
||||||
import RewardTotal from 'component/rewardTotal';
|
import RewardTotal from 'component/rewardTotal';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
|
||||||
import UserEmail from 'component/userEmail';
|
import UserEmail from 'component/userEmail';
|
||||||
import InvitePage from 'page/invite';
|
import InviteNew from 'component/inviteNew';
|
||||||
// import YoutubeChannelList from 'component/youtubeChannelList';
|
import InviteList from 'component/inviteList';
|
||||||
|
|
||||||
type Props = {
|
const AccountPage = () => {
|
||||||
// ytChannels: Array<any>,
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccountPage = (props: Props) => {
|
|
||||||
// const { ytChannels } = props;
|
|
||||||
// const hasYoutubeChannels = Boolean(ytChannels.length);
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
{/* @if TARGET='web' */}
|
<div className="columns">
|
||||||
<UserEmail />
|
<div>
|
||||||
{/* @endif */}
|
<RewardSummary />
|
||||||
<UnsupportedOnWeb />
|
<RewardTotal />
|
||||||
<div className={classnames({ 'card--disabled': IS_WEB })}>
|
</div>
|
||||||
<div className="columns">
|
<div>
|
||||||
<UserEmail />
|
<UserEmail />
|
||||||
<div>
|
<InviteNew />
|
||||||
<RewardSummary />
|
|
||||||
<RewardTotal />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/* {hasYoutubeChannels && <YoutubeChannelList />} */}
|
|
||||||
<InvitePage />
|
|
||||||
</div>
|
</div>
|
||||||
|
<InviteList />
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AccountPage;
|
export default AccountPage;
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectFollowedTags } from 'lbry-redux';
|
import { selectFollowedTags } from 'lbry-redux';
|
||||||
|
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
import DiscoverPage from './view';
|
import DiscoverPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
followedTags: selectFollowedTags(state),
|
followedTags: selectFollowedTags(state),
|
||||||
subscribedChannels: selectSubscriptions(state),
|
subscribedChannels: selectSubscriptions(state),
|
||||||
|
email: selectUserVerifiedEmail(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = {};
|
const perform = {};
|
||||||
|
|
|
@ -8,20 +8,20 @@ import Button from 'component/button';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
followedTags: Array<Tag>,
|
followedTags: Array<Tag>,
|
||||||
|
email: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function DiscoverPage(props: Props) {
|
function DiscoverPage(props: Props) {
|
||||||
const { followedTags } = props;
|
const { followedTags, email } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
{email && <TagsSelect showClose title={__('Customize Your Homepage')} />}
|
||||||
<ClaimListDiscover
|
<ClaimListDiscover
|
||||||
|
hideCustomization={IS_WEB && !email}
|
||||||
personalView
|
personalView
|
||||||
tags={followedTags.map(tag => tag.name)}
|
tags={followedTags.map(tag => tag.name)}
|
||||||
meta={<Button button="link" label={__('Customize')} navigate={`/$/${PAGES.FOLLOWING}`} />}
|
meta={<Button button="link" label={__('Customize')} navigate={`/$/${PAGES.FOLLOWING}`} />}
|
||||||
injectedItem={
|
|
||||||
<TagsSelect showClose title={__('Customize Your Homepage')} className="claim-preview--injected" />
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|
|
@ -124,7 +124,6 @@ class FilePage extends React.Component<Props> {
|
||||||
nsfw,
|
nsfw,
|
||||||
supportOption,
|
supportOption,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// File info
|
// File info
|
||||||
const { signing_channel: signingChannel } = claim;
|
const { signing_channel: signingChannel } = claim;
|
||||||
const channelName = signingChannel && signingChannel.name;
|
const channelName = signingChannel && signingChannel.name;
|
||||||
|
|
|
@ -35,14 +35,15 @@ function FileListPublished(props: Props) {
|
||||||
<Paginate totalPages={Math.ceil(Number(urlTotal) / Number(PAGE_SIZE))} loading={fetching} />
|
<Paginate totalPages={Math.ceil(Number(urlTotal) / Number(PAGE_SIZE))} loading={fetching} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="main--empty">
|
<section className="main--empty">
|
||||||
<section className="card card--section">
|
<div className=" section--small">
|
||||||
<h2 className="card__title">{__("It looks like you haven't published anything to LBRY yet.")}</h2>
|
<h2 className="section__title--large">{__('Nothing published to LBRY yet.')}</h2>
|
||||||
<div className="card__actions card__actions--center">
|
|
||||||
|
<div className="section__actions">
|
||||||
<Button button="primary" navigate="/$/publish" label={__('Publish something new')} />
|
<Button button="primary" navigate="/$/publish" label={__('Publish something new')} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
)}
|
)}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doFetchAccessToken, selectAccessToken, selectUser } from 'lbryinc';
|
import { doFetchAccessToken, selectAccessToken, selectUser } from 'lbryinc';
|
||||||
import { selectDaemonSettings } from 'redux/selectors/settings';
|
import { selectDaemonSettings } from 'redux/selectors/settings';
|
||||||
|
@ -10,7 +11,7 @@ const select = state => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch, ownProps) => ({
|
const perform = (dispatch, ownProps) => ({
|
||||||
doAuth: () => ownProps.history.push('/$/auth?redirect=help'),
|
doAuth: () => ownProps.history.push(`/$/${PAGES.AUTH}?redirect=help`),
|
||||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||||
import BusyIndicator from 'component/common/busy-indicator';
|
import BusyIndicator from 'component/common/busy-indicator';
|
||||||
import InviteNew from 'component/inviteNew';
|
import InviteNew from 'component/inviteNew';
|
||||||
import InviteList from 'component/inviteList';
|
import InviteList from 'component/inviteList';
|
||||||
|
import Page from 'component/page';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isPending: boolean,
|
isPending: boolean,
|
||||||
|
@ -26,16 +27,17 @@ class InvitePage extends React.PureComponent<Props> {
|
||||||
const { isPending, isFailed } = this.props;
|
const { isPending, isFailed } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Page>
|
||||||
{isPending && <BusyIndicator message={__('Checking your invite status')} />}
|
{isPending && <BusyIndicator message={__('Checking your invite status')} />}
|
||||||
{!isPending && isFailed && <span className="empty">{__('Failed to retrieve invite status.')}</span>}
|
{!isPending && isFailed && <span className="empty">{__('Failed to retrieve invite status.')}</span>}
|
||||||
{!isPending && !isFailed && (
|
{!isPending && !isFailed && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
{' '}
|
||||||
<InviteNew />
|
<InviteNew />
|
||||||
<InviteList />
|
<InviteList />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import Button from 'component/button';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { rewards as REWARD_TYPES } from 'lbryinc';
|
import { rewards as REWARD_TYPES } from 'lbryinc';
|
||||||
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
doAuth: () => void,
|
doAuth: () => void,
|
||||||
|
@ -31,7 +30,6 @@ class RewardsPage extends PureComponent<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchRewards();
|
this.props.fetchRewards();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPageHeader() {
|
renderPageHeader() {
|
||||||
const { user, daemonSettings } = this.props;
|
const { user, daemonSettings } = this.props;
|
||||||
|
|
||||||
|
@ -48,7 +46,7 @@ class RewardsPage extends PureComponent<Props> {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
navigate={`/$/${PAGES.AUTH}/signin?redirect=rewards`}
|
navigate={`/$/${PAGES.AUTH}?redirect=/$/${PAGES.REWARDS}`}
|
||||||
button="primary"
|
button="primary"
|
||||||
label={__('Unlock Rewards')}
|
label={__('Unlock Rewards')}
|
||||||
/>
|
/>
|
||||||
|
@ -95,7 +93,7 @@ class RewardsPage extends PureComponent<Props> {
|
||||||
renderUnclaimedRewards() {
|
renderUnclaimedRewards() {
|
||||||
const { fetching, rewards, user, daemonSettings, claimed } = this.props;
|
const { fetching, rewards, user, daemonSettings, claimed } = this.props;
|
||||||
|
|
||||||
if (daemonSettings && !daemonSettings.share_usage_data) {
|
if (!IS_WEB && daemonSettings && !daemonSettings.share_usage_data) {
|
||||||
return (
|
return (
|
||||||
<section className="card card--section">
|
<section className="card card--section">
|
||||||
<h2 className="card__title">{__('Disabled')}</h2>
|
<h2 className="card__title">{__('Disabled')}</h2>
|
||||||
|
@ -150,7 +148,6 @@ class RewardsPage extends PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
{IS_WEB && <UnsupportedOnWeb />}
|
|
||||||
{this.renderPageHeader()}
|
{this.renderPageHeader()}
|
||||||
{this.renderUnclaimedRewards()}
|
{this.renderUnclaimedRewards()}
|
||||||
{<RewardListClaimed />}
|
{<RewardListClaimed />}
|
||||||
|
|
|
@ -20,10 +20,11 @@ const select = (state, props) => {
|
||||||
try {
|
try {
|
||||||
uri = normalizeURI(path);
|
uri = normalizeURI(path);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Probably an old channel url, redirect to the vanity channel
|
|
||||||
// @routinghax
|
|
||||||
const match = path.match(/[#/:]/);
|
const match = path.match(/[#/:]/);
|
||||||
if (match && match.index) {
|
|
||||||
|
if (path === '$/') {
|
||||||
|
props.history.replace(`/`);
|
||||||
|
} else if (!path.startsWith('$/') && match && match.index) {
|
||||||
uri = `lbry://${path.slice(0, match.index)}`;
|
uri = `lbry://${path.slice(0, match.index)}`;
|
||||||
props.history.replace(`/${path.slice(0, match.index)}`);
|
props.history.replace(`/${path.slice(0, match.index)}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectEmailToVerify, selectUser } from 'lbryinc';
|
|
||||||
import { selectMyChannelClaims } from 'lbry-redux';
|
|
||||||
import SignUpPage from './view';
|
import SignUpPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = () => ({});
|
||||||
email: selectEmailToVerify(state),
|
const perform = () => ({});
|
||||||
user: selectUser(state),
|
|
||||||
channels: selectMyChannelClaims(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
select,
|
select,
|
||||||
null
|
perform
|
||||||
)(SignUpPage);
|
)(SignUpPage);
|
||||||
|
|
|
@ -1,42 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import UserSignIn from 'component/userSignIn';
|
import UserSignIn from 'component/userSignIn';
|
||||||
import UserFirstChannel from 'component/userFirstChannel';
|
|
||||||
import UserVerify from 'component/userVerify';
|
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
|
|
||||||
type Props = {
|
export default function SignInPage() {
|
||||||
user: ?User,
|
|
||||||
channels: ?Array<ChannelClaim>,
|
|
||||||
location: { search: string },
|
|
||||||
history: { replace: string => void },
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function SignInPage(props: Props) {
|
|
||||||
const { user, channels, location, history } = props;
|
|
||||||
const { search } = location;
|
|
||||||
const urlParams = new URLSearchParams(search);
|
|
||||||
const redirect = urlParams.get('redirect');
|
|
||||||
const hasVerifiedEmail = user && user.has_verified_email;
|
|
||||||
const rewardsApproved = user && user.is_reward_approved;
|
|
||||||
const channelCount = channels ? channels.length : 0;
|
|
||||||
const showWelcome = !hasVerifiedEmail || channelCount === 0;
|
|
||||||
|
|
||||||
if (rewardsApproved && channelCount > 0) {
|
|
||||||
history.replace(redirect ? `/$/${redirect}` : '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page fullscreen className="main--auth-page">
|
<Page fullscreen className="main--auth-page">
|
||||||
{showWelcome && (
|
<UserSignIn />
|
||||||
<div className="columns">
|
|
||||||
{!hasVerifiedEmail && <UserSignIn />}
|
|
||||||
{hasVerifiedEmail && channelCount === 0 && <UserFirstChannel />}
|
|
||||||
<div style={{ width: '100%', height: '20rem', borderRadius: 20, backgroundColor: '#ffc7e6' }} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasVerifiedEmail && channelCount > 0 && <UserVerify />}
|
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
|
||||||
import TransactionList from 'component/transactionList';
|
import TransactionList from 'component/transactionList';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
fetchMyClaims: () => void,
|
fetchMyClaims: () => void,
|
||||||
|
@ -26,12 +24,7 @@ class TransactionHistoryPage extends React.PureComponent<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
{IS_WEB && <UnsupportedOnWeb />}
|
<section className="card">
|
||||||
<section
|
|
||||||
className={classnames('card', {
|
|
||||||
'card--disabled': IS_WEB,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<TransactionList
|
<TransactionList
|
||||||
transactions={filteredTransactionPage}
|
transactions={filteredTransactionPage}
|
||||||
transactionCount={filteredTransactionsCount}
|
transactionCount={filteredTransactionsCount}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { ipcRenderer, remote } from 'electron';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import {
|
import {
|
||||||
Lbry,
|
Lbry,
|
||||||
doBalanceSubscribe,
|
doBalanceSubscribe,
|
||||||
|
@ -13,6 +14,8 @@ import {
|
||||||
doError,
|
doError,
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
makeSelectClaimIsMine,
|
makeSelectClaimIsMine,
|
||||||
|
doPopulateUserSettings,
|
||||||
|
doFetchChannelListMine,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import Native from 'native';
|
import Native from 'native';
|
||||||
import { doFetchDaemonSettings } from 'redux/actions/settings';
|
import { doFetchDaemonSettings } from 'redux/actions/settings';
|
||||||
|
@ -28,10 +31,12 @@ import {
|
||||||
selectUpgradeTimer,
|
selectUpgradeTimer,
|
||||||
selectModal,
|
selectModal,
|
||||||
} from 'redux/selectors/app';
|
} from 'redux/selectors/app';
|
||||||
import { doAuthenticate } from 'lbryinc';
|
import { Lbryio, doAuthenticate } from 'lbryinc';
|
||||||
import { lbrySettings as config, version as appVersion } from 'package.json';
|
import { lbrySettings as config, version as appVersion } from 'package.json';
|
||||||
import { push } from 'connected-react-router';
|
import { push } from 'connected-react-router';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
|
import { deleteAuthToken } from 'util/saved-passwords';
|
||||||
|
import cookie from 'cookie';
|
||||||
|
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
const { autoUpdater } = remote.require('electron-updater');
|
const { autoUpdater } = remote.require('electron-updater');
|
||||||
|
@ -322,13 +327,12 @@ export function doAlertError(errorList) {
|
||||||
export function doDaemonReady() {
|
export function doDaemonReady() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
dispatch(doAuthenticate(appVersion));
|
dispatch(doAuthenticate(appVersion));
|
||||||
dispatch({ type: ACTIONS.DAEMON_READY });
|
dispatch({ type: ACTIONS.DAEMON_READY });
|
||||||
|
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
dispatch(doFetchDaemonSettings());
|
|
||||||
dispatch(doBalanceSubscribe());
|
dispatch(doBalanceSubscribe());
|
||||||
|
dispatch(doFetchDaemonSettings());
|
||||||
dispatch(doFetchFileInfosAndPublishedClaims());
|
dispatch(doFetchFileInfosAndPublishedClaims());
|
||||||
if (!selectIsUpgradeSkipped(state)) {
|
if (!selectIsUpgradeSkipped(state)) {
|
||||||
dispatch(doCheckUpgradeAvailable());
|
dispatch(doCheckUpgradeAvailable());
|
||||||
|
@ -414,7 +418,7 @@ export function doConditionalAuthNavigate(newSession) {
|
||||||
const modal = selectModal(state);
|
const modal = selectModal(state);
|
||||||
|
|
||||||
if (newSession || (modal && modal.id !== MODALS.EMAIL_COLLECTION)) {
|
if (newSession || (modal && modal.id !== MODALS.EMAIL_COLLECTION)) {
|
||||||
dispatch(push('/$/auth'));
|
dispatch(push(`/$/${PAGES.AUTH}`));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -439,3 +443,32 @@ export function doAnalyticsView(uri, timeToStart) {
|
||||||
return analytics.apiLogView(uri, outpoint, claimId, timeToStart);
|
return analytics.apiLogView(uri, outpoint, claimId, timeToStart);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doSignIn() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
// The balance is subscribed to on launch for desktop
|
||||||
|
// @if TARGET='web'
|
||||||
|
const { auth_token: authToken } = cookie.parse(document.cookie);
|
||||||
|
Lbry.setApiHeader('X-Lbry-Auth-Token', authToken);
|
||||||
|
|
||||||
|
dispatch(doBalanceSubscribe());
|
||||||
|
dispatch(doCheckSubscriptionsInit());
|
||||||
|
dispatch(doFetchChannelListMine());
|
||||||
|
// @endif
|
||||||
|
|
||||||
|
Lbryio.call('user_settings', 'get').then(settings => {
|
||||||
|
dispatch(doPopulateUserSettings(settings));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doSignOut() {
|
||||||
|
return dispatch => {
|
||||||
|
deleteAuthToken()
|
||||||
|
.then(window.persistor.purge)
|
||||||
|
.then(() => {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.catch(() => location.reload());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
|
import { parseURI, ACTIONS as LBRY_REDUX_ACTIONS } from 'lbry-redux';
|
||||||
import { VIEW_ALL } from 'constants/subscriptions';
|
import { VIEW_ALL } from 'constants/subscriptions';
|
||||||
import { handleActions } from 'util/redux-utils';
|
import { handleActions } from 'util/redux-utils';
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ const defaultState: SubscriptionState = {
|
||||||
loadingSuggested: false,
|
loadingSuggested: false,
|
||||||
firstRunCompleted: false,
|
firstRunCompleted: false,
|
||||||
showSuggestedSubs: false,
|
showSuggestedSubs: false,
|
||||||
|
enabledChannelNotifications: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
|
@ -134,6 +136,39 @@ export default handleActions(
|
||||||
...state,
|
...state,
|
||||||
loadingSuggested: false,
|
loadingSuggested: false,
|
||||||
}),
|
}),
|
||||||
|
[LBRY_REDUX_ACTIONS.USER_STATE_POPULATE]: (
|
||||||
|
state: SubscriptionState,
|
||||||
|
action: { data: { subscriptions: ?Array<string> } }
|
||||||
|
) => {
|
||||||
|
const { subscriptions } = action.data;
|
||||||
|
let newSubscriptions;
|
||||||
|
|
||||||
|
if (!subscriptions) {
|
||||||
|
newSubscriptions = state.subscriptions;
|
||||||
|
} else {
|
||||||
|
const parsedSubscriptions = subscriptions.map(uri => {
|
||||||
|
const { channelName } = parseURI(uri);
|
||||||
|
|
||||||
|
return {
|
||||||
|
uri,
|
||||||
|
channelName: `@${channelName}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (!state.subscriptions || !state.subscriptions.length) {
|
||||||
|
newSubscriptions = parsedSubscriptions;
|
||||||
|
} else {
|
||||||
|
const map = {};
|
||||||
|
newSubscriptions = parsedSubscriptions.concat(state.subscriptions).filter(sub => {
|
||||||
|
return map[sub.uri] ? false : (map[sub.uri] = true);
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
subscriptions: newSubscriptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
defaultState
|
defaultState
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
@import 'component/file-render';
|
@import 'component/file-render';
|
||||||
@import 'component/form-field';
|
@import 'component/form-field';
|
||||||
@import 'component/header';
|
@import 'component/header';
|
||||||
|
@import 'component/icon';
|
||||||
@import 'component/item-list';
|
@import 'component/item-list';
|
||||||
@import 'component/main';
|
@import 'component/main';
|
||||||
@import 'component/markdown-editor';
|
@import 'component/markdown-editor';
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
@import 'component/pagination';
|
@import 'component/pagination';
|
||||||
@import 'component/placeholder';
|
@import 'component/placeholder';
|
||||||
@import 'component/search';
|
@import 'component/search';
|
||||||
|
@import 'component/section';
|
||||||
@import 'component/snack-bar';
|
@import 'component/snack-bar';
|
||||||
@import 'component/spinner';
|
@import 'component/spinner';
|
||||||
@import 'component/splash';
|
@import 'component/splash';
|
||||||
|
|
|
@ -15,9 +15,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--primary {
|
.button--primary {
|
||||||
|
background-color: $lbry-teal-5;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $lbry-teal-4;
|
background-color: $lbry-teal-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play/View button that is overlayed ontop of the video player
|
// Play/View button that is overlayed ontop of the video player
|
||||||
|
|
|
@ -55,6 +55,12 @@
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card--inline {
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
// C A R D
|
// C A R D
|
||||||
// A C T I O N S
|
// A C T I O N S
|
||||||
|
|
||||||
|
@ -195,12 +201,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__title--large {
|
|
||||||
@extend .card__title;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: var(--font-heading);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__title--between {
|
.card__title--between {
|
||||||
@extend .card__title;
|
@extend .card__title;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -214,3 +214,29 @@
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card__header {
|
||||||
|
padding: var(--spacing-large);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__body {
|
||||||
|
padding: var(--spacing-large);
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__main-actions {
|
||||||
|
padding: var(--spacing-large);
|
||||||
|
background-color: rgba($lbry-blue-1, 0.1);
|
||||||
|
color: darken($lbry-gray-5, 15%);
|
||||||
|
font-size: var(--font-body);
|
||||||
|
|
||||||
|
[data-mode='dark'] & {
|
||||||
|
background-color: var(--dm-color-04);
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__body--with-icon,
|
||||||
|
.card__main-actions--with-icon {
|
||||||
|
padding-left: 7.5rem;
|
||||||
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ select,
|
||||||
textarea {
|
textarea {
|
||||||
border-color: lighten($lbry-black, 20%);
|
border-color: lighten($lbry-black, 20%);
|
||||||
border-radius: var(--input-border-radius);
|
border-radius: var(--input-border-radius);
|
||||||
|
background-color: $lbry-white;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +68,7 @@ fieldset-section {
|
||||||
label {
|
label {
|
||||||
width: auto;
|
width: auto;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
|
color: lighten($lbry-black, 20%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +92,6 @@ radio-element {
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
color: lighten($lbry-black, 20%);
|
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
margin-left: var(--spacing-miniscule);
|
margin-left: var(--spacing-miniscule);
|
||||||
font-size: var(--font-body);
|
font-size: var(--font-body);
|
||||||
|
@ -184,9 +185,12 @@ fieldset-group {
|
||||||
height: var(--input-height);
|
height: var(--input-height);
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
|
border-top-left-radius: var(--input-border-radius);
|
||||||
|
border-bottom-left-radius: var(--input-border-radius);
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
border-color: $lbry-black;
|
border-color: $lbry-black;
|
||||||
color: $lbry-gray-4;
|
color: $lbry-gray-4;
|
||||||
|
background-color: $lbry-white;
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
border-color: $lbry-gray-4;
|
border-color: $lbry-gray-4;
|
||||||
|
@ -275,7 +279,6 @@ fieldset-section {
|
||||||
max-width: 12em;
|
max-width: 12em;
|
||||||
background-position: 95% center;
|
background-position: 95% center;
|
||||||
background-size: 1.2rem;
|
background-size: 1.2rem;
|
||||||
background-color: $lbry-white;
|
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -305,7 +308,6 @@ fieldset-section {
|
||||||
background-color: rgba($lbry-gray-1, 0.5);
|
background-color: rgba($lbry-gray-1, 0.5);
|
||||||
border: 1px solid $lbry-gray-1;
|
border: 1px solid $lbry-gray-1;
|
||||||
color: $lbry-gray-5;
|
color: $lbry-gray-5;
|
||||||
flex: 1;
|
|
||||||
padding: 0.2rem 0.75rem;
|
padding: 0.2rem 0.75rem;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
|
@ -324,6 +326,10 @@ fieldset-section {
|
||||||
margin-bottom: var(--spacing-large);
|
margin-bottom: var(--spacing-large);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-field--short {
|
||||||
|
width: 25em;
|
||||||
|
}
|
||||||
|
|
||||||
.form-field--price-amount {
|
.form-field--price-amount {
|
||||||
width: 7em;
|
width: 7em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,11 +54,15 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.button {
|
> .button:only-child {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header__menu--small {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.header__navigation-arrows {
|
.header__navigation-arrows {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: var(--spacing-small);
|
margin-right: var(--spacing-small);
|
||||||
|
|
18
src/ui/scss/component/_icon.scss
Normal file
18
src/ui/scss/component/_icon.scss
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
.icon__wrapper {
|
||||||
|
@extend .card__subtitle;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
height: 3.5rem;
|
||||||
|
width: 3.5rem;
|
||||||
|
border-radius: calc(3.5rem / 2);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
stroke: $lbry-gray-5;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
width: calc(100% - var(--side-nav-width) - var(--spacing-main-padding));
|
position: relative;
|
||||||
|
width: calc(100% - var(--side-nav-width) - var(--spacing-large));
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -43,7 +44,7 @@
|
||||||
|
|
||||||
.main--auth-page {
|
.main--auth-page {
|
||||||
max-width: 60rem;
|
max-width: 60rem;
|
||||||
margin-top: calc(var(--spacing-main-padding) * 2);
|
margin-top: var(--spacing-main-padding);
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
@ -63,22 +64,15 @@
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main--fullscreen {
|
.main--contained {
|
||||||
width: 100vw;
|
max-width: 35rem;
|
||||||
height: 100vh;
|
min-width: 25rem;
|
||||||
z-index: 9999;
|
margin: auto;
|
||||||
background-color: $lbry-white;
|
margin-top: 5rem;
|
||||||
position: absolute;
|
}
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
|
|
||||||
* {
|
.main--full-width {
|
||||||
z-index: 10000;
|
width: 100%;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main__status {
|
.main__status {
|
||||||
|
|
|
@ -9,9 +9,22 @@
|
||||||
|
|
||||||
.navigation--placeholder {
|
.navigation--placeholder {
|
||||||
@extend .navigation;
|
@extend .navigation;
|
||||||
height: 80vh;
|
padding: 2rem 1.5rem;
|
||||||
background-color: $lbry-blue-1;
|
|
||||||
border-radius: var(--card-radius);
|
border-radius: var(--card-radius);
|
||||||
|
font-size: var(--font-title);
|
||||||
|
font-weight: 600;
|
||||||
|
color: $lbry-white;
|
||||||
|
position: relative;
|
||||||
|
background-color: $lbry-black;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-links {
|
.navigation-links {
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
table,
|
table,
|
||||||
.table {
|
.table {
|
||||||
|
background-color: transparent;
|
||||||
margin: var(--spacing-small) 0;
|
margin: var(--spacing-small) 0;
|
||||||
|
padding-top: var(--spacing-small);
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
th {
|
th {
|
||||||
border-bottom: 2px solid $lbry-white;
|
border-bottom: 2px solid $lbry-white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,11 @@ $main: $lbry-teal-5;
|
||||||
|
|
||||||
.tags--remove {
|
.tags--remove {
|
||||||
@extend .tags;
|
@extend .tags;
|
||||||
@extend .ul--no-style;
|
|
||||||
margin-bottom: var(--spacing-large);
|
.tag {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: var(--spacing-small);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags--vertical {
|
.tags--vertical {
|
||||||
|
@ -31,8 +34,24 @@ $main: $lbry-teal-5;
|
||||||
margin: var(--spacing-large) 0;
|
margin: var(--spacing-large) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags__empty-message {
|
.tags__input-wrapper {
|
||||||
margin-top: var(--spacing-medium);
|
display: flex;
|
||||||
|
|
||||||
|
// Nested style needed for more specificity
|
||||||
|
.tag__input {
|
||||||
|
@extend .tag--remove;
|
||||||
|
border: 1px dashed;
|
||||||
|
border-color: $lbry-teal-5;
|
||||||
|
background-color: mix($lbry-teal-1, $lbry-white, 10%);
|
||||||
|
height: auto;
|
||||||
|
padding: calc(var(--spacing-miniscule) - 1px) var(--spacing-small);
|
||||||
|
margin-top: -2px;
|
||||||
|
border: 1px dashed lighten($lbry-teal-5, 10%);
|
||||||
|
|
||||||
|
::placeholder {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
|
@ -68,7 +87,11 @@ $main: $lbry-teal-5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag--add {
|
.tag--add {
|
||||||
background-color: lighten($lbry-teal-5, 60%);
|
background-color: $lbry-teal-5;
|
||||||
|
color: $lbry-white;
|
||||||
|
.icon {
|
||||||
|
stroke: $lbry-white;
|
||||||
|
}
|
||||||
|
|
||||||
&.tag--mature {
|
&.tag--mature {
|
||||||
@extend .badge--mature;
|
@extend .badge--mature;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
margin-right: var(--spacing-main-padding);
|
margin-right: var(--spacing-large);
|
||||||
font-size: var(--font-label);
|
font-size: var(--font-label);
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
.menu__title,
|
.menu__title,
|
||||||
.menu__link {
|
.menu__link {
|
||||||
color: lighten($lbry-black, 20%);
|
color: lighten($lbry-black, 20%);
|
||||||
|
color: $lbry-gray-5;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
stroke: $lbry-gray-5;
|
stroke: $lbry-gray-5;
|
||||||
|
@ -104,7 +105,7 @@
|
||||||
.menu__link {
|
.menu__link {
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: var(--spacing-small);
|
margin-right: var(--spacing-small);
|
||||||
stroke: $lbry-gray-5;
|
stroke: $lbry-gray-4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
77
src/ui/scss/component/section.scss
Normal file
77
src/ui/scss/component/section.scss
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
.section {
|
||||||
|
margin-top: var(--spacing-medium);
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section--large {
|
||||||
|
margin-bottom: var(--spacing-main-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section--small {
|
||||||
|
max-width: 30rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__flex {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
& > :first-child {
|
||||||
|
margin-right: var(--spacing-large);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__title {
|
||||||
|
text-align: left;
|
||||||
|
font-size: var(--font-section-title);
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__title--large {
|
||||||
|
@extend .section__title;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: var(--font-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__subtitle {
|
||||||
|
color: $lbry-gray-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__divider {
|
||||||
|
padding: var(--spacing-large) 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: $lbry-gray-4;
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--font-title);
|
||||||
|
font-weight: 500;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
padding: 0 var(--spacing-large);
|
||||||
|
display: inline-block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: var(--spacing-medium);
|
||||||
|
font-size: var(--font-body);
|
||||||
|
|
||||||
|
&:only-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> *:not(:last-child) {
|
||||||
|
margin-right: var(--spacing-medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__body {
|
||||||
|
margin-top: var(--spacing-large);
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ body {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
background-color: var(--color-background);
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
background-color: var(--dm-color-08);
|
background-color: var(--dm-color-08);
|
||||||
|
@ -48,13 +48,15 @@ p {
|
||||||
|
|
||||||
ul,
|
ul,
|
||||||
ol {
|
ol {
|
||||||
margin-bottom: var(--spacing-large);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul,
|
||||||
|
ol {
|
||||||
list-style: initial;
|
list-style: initial;
|
||||||
|
margin-bottom: var(--spacing-large);
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
list-style-position: outside;
|
||||||
margin: var(--spacing-medium) 0;
|
margin: var(--spacing-medium) 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,10 +34,12 @@ $large-breakpoint: 1921px;
|
||||||
--font-label: 0.9em;
|
--font-label: 0.9em;
|
||||||
--font-subtext: 1em;
|
--font-subtext: 1em;
|
||||||
--font-title: 1.6em;
|
--font-title: 1.6em;
|
||||||
|
--font-section-title: 2rem;
|
||||||
--font-heading: 3rem;
|
--font-heading: 3rem;
|
||||||
|
|
||||||
// Color
|
// Color
|
||||||
--color-background: #270f34;
|
--color-background: #f7f7f7;
|
||||||
|
--color-background--splash: #270f34;
|
||||||
|
|
||||||
// Dark Mode
|
// Dark Mode
|
||||||
--dm-color-01: #ddd;
|
--dm-color-01: #ddd;
|
||||||
|
|
|
@ -8,6 +8,8 @@ import thunk from 'redux-thunk';
|
||||||
import { createHashHistory, createBrowserHistory } from 'history';
|
import { createHashHistory, createBrowserHistory } from 'history';
|
||||||
import { routerMiddleware } from 'connected-react-router';
|
import { routerMiddleware } from 'connected-react-router';
|
||||||
import createRootReducer from './reducers';
|
import createRootReducer from './reducers';
|
||||||
|
import { Lbryio } from 'lbryinc';
|
||||||
|
import isEqual from 'util/deep-equal';
|
||||||
|
|
||||||
function isFunction(object) {
|
function isFunction(object) {
|
||||||
return typeof object === 'function';
|
return typeof object === 'function';
|
||||||
|
@ -53,13 +55,12 @@ const whiteListedReducers = [
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
'publish',
|
'publish',
|
||||||
'wallet',
|
'wallet',
|
||||||
|
'tags',
|
||||||
// 'fileInfo',
|
// 'fileInfo',
|
||||||
// @endif
|
// @endif
|
||||||
'content',
|
'content',
|
||||||
'subscriptions',
|
|
||||||
'app',
|
'app',
|
||||||
'search',
|
'search',
|
||||||
'tags',
|
|
||||||
'blocked',
|
'blocked',
|
||||||
'settings',
|
'settings',
|
||||||
];
|
];
|
||||||
|
@ -69,6 +70,7 @@ const transforms = [
|
||||||
walletFilter,
|
walletFilter,
|
||||||
fileInfoFilter,
|
fileInfoFilter,
|
||||||
blockedFilter,
|
blockedFilter,
|
||||||
|
tagsFilter,
|
||||||
// @endif
|
// @endif
|
||||||
appFilter,
|
appFilter,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
|
@ -106,6 +108,29 @@ const store = createStore(
|
||||||
composeEnhancers(applyMiddleware(...middleware))
|
composeEnhancers(applyMiddleware(...middleware))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let currentPayload;
|
||||||
|
store.subscribe(() => {
|
||||||
|
const state = store.getState();
|
||||||
|
const subscriptions = state.subscriptions.subscriptions.map(({ uri }) => uri);
|
||||||
|
const tags = state.tags.followedTags;
|
||||||
|
const authToken = state.user.accessToken;
|
||||||
|
|
||||||
|
const newPayload = {
|
||||||
|
version: '0.1',
|
||||||
|
shared: {
|
||||||
|
subscriptions,
|
||||||
|
tags,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isEqual(newPayload, currentPayload)) {
|
||||||
|
currentPayload = newPayload;
|
||||||
|
if (authToken) {
|
||||||
|
Lbryio.call('user_settings', 'set', { settings: newPayload });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const persistor = persistStore(store);
|
const persistor = persistStore(store);
|
||||||
window.persistor = persistor;
|
window.persistor = persistor;
|
||||||
|
|
||||||
|
|
117
src/ui/util/deep-equal.js
Normal file
117
src/ui/util/deep-equal.js
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
// underscore's deep equal function
|
||||||
|
// https://github.com/jashkenas/underscore/blob/master/underscore.js#L1189
|
||||||
|
|
||||||
|
export default function isEqual(a, b, aStack, bStack) {
|
||||||
|
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
||||||
|
// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
|
||||||
|
if (a === b) return a !== 0 || 1 / a === 1 / b;
|
||||||
|
// `null` or `undefined` only equal to itself (strict comparison).
|
||||||
|
if (a == null || b == null) return false;
|
||||||
|
// `NaN`s are equivalent, but non-reflexive.
|
||||||
|
if (a !== a) return b !== b;
|
||||||
|
// Exhaust primitive checks
|
||||||
|
var type = typeof a;
|
||||||
|
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
|
||||||
|
return deepEq(a, b, aStack, bStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepEq(a, b, aStack, bStack) {
|
||||||
|
// Compare `[[Class]]` names.
|
||||||
|
var className = toString.call(a);
|
||||||
|
if (className !== toString.call(b)) return false;
|
||||||
|
switch (className) {
|
||||||
|
// Strings, numbers, regular expressions, dates, and booleans are compared by value.
|
||||||
|
case '[object RegExp]':
|
||||||
|
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
|
||||||
|
case '[object String]':
|
||||||
|
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
||||||
|
// equivalent to `new String("5")`.
|
||||||
|
return '' + a === '' + b;
|
||||||
|
case '[object Number]':
|
||||||
|
// `NaN`s are equivalent, but non-reflexive.
|
||||||
|
// Object(NaN) is equivalent to NaN.
|
||||||
|
if (+a !== +a) return +b !== +b;
|
||||||
|
// An `egal` comparison is performed for other numeric values.
|
||||||
|
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
|
||||||
|
case '[object Date]':
|
||||||
|
case '[object Boolean]':
|
||||||
|
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
||||||
|
// millisecond representations. Note that invalid dates with millisecond representations
|
||||||
|
// of `NaN` are not equivalent.
|
||||||
|
return +a === +b;
|
||||||
|
case '[object Symbol]':
|
||||||
|
return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
var areArrays = className === '[object Array]';
|
||||||
|
if (!areArrays) {
|
||||||
|
if (typeof a != 'object' || typeof b != 'object') return false;
|
||||||
|
|
||||||
|
// Objects with different constructors are not equivalent, but `Object`s or `Array`s
|
||||||
|
// from different frames are.
|
||||||
|
var aCtor = a.constructor,
|
||||||
|
bCtor = b.constructor;
|
||||||
|
if (
|
||||||
|
aCtor !== bCtor &&
|
||||||
|
!(
|
||||||
|
typeof aCtor === 'function' &&
|
||||||
|
aCtor instanceof aCtor &&
|
||||||
|
typeof bCtor === 'function' &&
|
||||||
|
bCtor instanceof bCtor
|
||||||
|
) &&
|
||||||
|
('constructor' in a && 'constructor' in b)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
||||||
|
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
||||||
|
|
||||||
|
// Initializing stack of traversed objects.
|
||||||
|
// It's done here since we only need them for objects and arrays comparison.
|
||||||
|
aStack = aStack || [];
|
||||||
|
bStack = bStack || [];
|
||||||
|
var length = aStack.length;
|
||||||
|
while (length--) {
|
||||||
|
// Linear search. Performance is inversely proportional to the number of
|
||||||
|
// unique nested structures.
|
||||||
|
if (aStack[length] === a) return bStack[length] === b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the first object to the stack of traversed objects.
|
||||||
|
aStack.push(a);
|
||||||
|
bStack.push(b);
|
||||||
|
|
||||||
|
// Recursively compare objects and arrays.
|
||||||
|
if (areArrays) {
|
||||||
|
// Compare array lengths to determine if a deep comparison is necessary.
|
||||||
|
length = a.length;
|
||||||
|
if (length !== b.length) return false;
|
||||||
|
// Deep compare the contents, ignoring non-numeric properties.
|
||||||
|
while (length--) {
|
||||||
|
if (!isEqual(a[length], b[length], aStack, bStack)) return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Deep compare objects.
|
||||||
|
var keys = Object.keys(a),
|
||||||
|
key;
|
||||||
|
length = keys.length;
|
||||||
|
// Ensure that both objects contain the same number of properties before comparing deep equality.
|
||||||
|
if (Object.keys(b).length !== length) return false;
|
||||||
|
while (length--) {
|
||||||
|
// Deep compare each member
|
||||||
|
key = keys[length];
|
||||||
|
if (!(has(b, key) && isEqual(a[key], b[key], aStack, bStack))) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove the first object from the stack of traversed objects.
|
||||||
|
aStack.pop();
|
||||||
|
bStack.pop();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function has(obj, path) {
|
||||||
|
return obj != null && hasOwnProperty.call(obj, path);
|
||||||
|
}
|
||||||
|
/* eslint-enable */
|
|
@ -26,13 +26,19 @@ export const getSavedPassword = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteSavedPassword = () => {
|
export const deleteAuthToken = () => {
|
||||||
return new Promise(
|
return new Promise(
|
||||||
resolve => {
|
resolve => {
|
||||||
ipcRenderer.once('delete-password-response', (event, success) => {
|
// @if TARGET='app'
|
||||||
resolve(success);
|
ipcRenderer.once('delete-auth-token-response', (event, success) => {
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
ipcRenderer.send('delete-password');
|
ipcRenderer.send('delete-auth-token');
|
||||||
|
// @endif;
|
||||||
|
// @if TARGET='web'
|
||||||
|
document.cookie = 'auth_token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT';
|
||||||
|
resolve();
|
||||||
|
// @endif
|
||||||
},
|
},
|
||||||
reject => {
|
reject => {
|
||||||
reject(false);
|
reject(false);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"404": "404",
|
||||||
"Thumbnail Image": "Thumbnail Image",
|
"Thumbnail Image": "Thumbnail Image",
|
||||||
"OK": "OK",
|
"OK": "OK",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
|
@ -708,6 +709,6 @@
|
||||||
"Tip %amount% LBC": "Tip %amount% LBC",
|
"Tip %amount% LBC": "Tip %amount% LBC",
|
||||||
"Not enough credits": "Not enough credits",
|
"Not enough credits": "Not enough credits",
|
||||||
"You have %credit_amount% in unclaimed rewards.": "You have %credit_amount% in unclaimed rewards.",
|
"You have %credit_amount% in unclaimed rewards.": "You have %credit_amount% in unclaimed rewards.",
|
||||||
"You haven't downloaded anything from LBRY yet.": "You haven't downloaded anything from LBRY yet.",
|
"URI does not include name.": "URI does not include name.",
|
||||||
"Explore new content": "Explore new content"
|
"to fix it. If that doesn't work, press CMD/CTRL-R to reset to the homepage.": "to fix it. If that doesn't work, press CMD/CTRL-R to reset to the homepage."
|
||||||
}
|
}
|
||||||
|
|
124
yarn.lock
124
yarn.lock
|
@ -842,18 +842,6 @@
|
||||||
ajv "^6.1.0"
|
ajv "^6.1.0"
|
||||||
ajv-keywords "^3.1.0"
|
ajv-keywords "^3.1.0"
|
||||||
|
|
||||||
"@emotion/is-prop-valid@^0.7.3":
|
|
||||||
version "0.7.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz#a6bf4fa5387cbba59d44e698a4680f481a8da6cc"
|
|
||||||
integrity sha512-uxJqm/sqwXw3YPA5GXX365OBcJGFtxUVkB6WyezqFHlNe9jqUWH5ur2O2M8dGBz61kn1g3ZBlzUunFQXQIClhA==
|
|
||||||
dependencies:
|
|
||||||
"@emotion/memoize" "0.7.1"
|
|
||||||
|
|
||||||
"@emotion/memoize@0.7.1":
|
|
||||||
version "0.7.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f"
|
|
||||||
integrity sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==
|
|
||||||
|
|
||||||
"@exponent/electron-cookies@^2.0.0":
|
"@exponent/electron-cookies@^2.0.0":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@exponent/electron-cookies/-/electron-cookies-2.0.0.tgz#4cf8dcf851454036cc524c40e9e482fc4e23f2d9"
|
resolved "https://registry.yarnpkg.com/@exponent/electron-cookies/-/electron-cookies-2.0.0.tgz#4cf8dcf851454036cc524c40e9e482fc4e23f2d9"
|
||||||
|
@ -904,21 +892,6 @@
|
||||||
universal-user-agent "^2.0.0"
|
universal-user-agent "^2.0.0"
|
||||||
url-template "^2.0.8"
|
url-template "^2.0.8"
|
||||||
|
|
||||||
"@popmotion/easing@^1.0.1":
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@popmotion/easing/-/easing-1.0.2.tgz#17d925c45b4bf44189e5a38038d149df42d8c0b4"
|
|
||||||
integrity sha512-IkdW0TNmRnWTeWI7aGQIVDbKXPWHVEYdGgd5ZR4SH/Ty/61p63jCjrPxX1XrR7IGkl08bjhJROStD7j+RKgoIw==
|
|
||||||
|
|
||||||
"@popmotion/popcorn@^0.4.0":
|
|
||||||
version "0.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@popmotion/popcorn/-/popcorn-0.4.0.tgz#089ba62a7a8801ba18876d42149d4289992b926c"
|
|
||||||
integrity sha512-vrCzLNT/ZscfrviWirZwRvpD9hSzCTNRxgUwHb1xvDNKJaXSNKTAeS/aiIdV3A/o/09Gu9CyMI0BpVGhK78wnw==
|
|
||||||
dependencies:
|
|
||||||
"@popmotion/easing" "^1.0.1"
|
|
||||||
framesync "^4.0.1"
|
|
||||||
hey-listen "^1.0.8"
|
|
||||||
style-value-types "^3.1.4"
|
|
||||||
|
|
||||||
"@posthtml/esm@^1.0.0":
|
"@posthtml/esm@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
|
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
|
||||||
|
@ -1057,11 +1030,6 @@
|
||||||
"@types/minimatch" "*"
|
"@types/minimatch" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/invariant@^2.2.29":
|
|
||||||
version "2.2.29"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.29.tgz#aa845204cd0a289f65d47e0de63a6a815e30cc66"
|
|
||||||
integrity sha512-lRVw09gOvgviOfeUrKc/pmTiRZ7g7oDOU6OAutyuSHpm1/o2RaBQvRhgK8QEdu+FFuw/wnWb29A/iuxv9i8OpQ==
|
|
||||||
|
|
||||||
"@types/minimatch@*":
|
"@types/minimatch@*":
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
|
@ -1072,7 +1040,7 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.2.tgz#3452a24edf9fea138b48fad4a0a028a683da1e40"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.2.tgz#3452a24edf9fea138b48fad4a0a028a683da1e40"
|
||||||
integrity sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==
|
integrity sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==
|
||||||
|
|
||||||
"@types/node@^10.0.5", "@types/node@^10.12.18":
|
"@types/node@^10.12.18":
|
||||||
version "10.14.7"
|
version "10.14.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.7.tgz#1854f0a9aa8d2cd6818d607b3d091346c6730362"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.7.tgz#1854f0a9aa8d2cd6818d607b3d091346c6730362"
|
||||||
integrity sha512-on4MmIDgHXiuJDELPk1NFaKVUxxCFr37tm8E9yN6rAiF5Pzp/9bBfBHkoexqRiY+hk/Z04EJU9kKEb59YqJ82A==
|
integrity sha512-on4MmIDgHXiuJDELPk1NFaKVUxxCFr37tm8E9yN6rAiF5Pzp/9bBfBHkoexqRiY+hk/Z04EJU9kKEb59YqJ82A==
|
||||||
|
@ -5087,13 +5055,6 @@ fragment-cache@^0.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
map-cache "^0.2.2"
|
map-cache "^0.2.2"
|
||||||
|
|
||||||
framesync@^4.0.0, framesync@^4.0.1:
|
|
||||||
version "4.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/framesync/-/framesync-4.0.2.tgz#b03b62852f12b0d80086b60834b089718f03cda5"
|
|
||||||
integrity sha512-hQLD5NURHmzB4Symo6JJ5HDw2TWwhr6T3gw9aChNMsZvkxcD8U8Gcz/hllAOOMGE+HO3ScpRPahpXDQRgF19JQ==
|
|
||||||
dependencies:
|
|
||||||
hey-listen "^1.0.5"
|
|
||||||
|
|
||||||
fresh@0.5.2:
|
fresh@0.5.2:
|
||||||
version "0.5.2"
|
version "0.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||||
|
@ -5637,11 +5598,6 @@ hex-color-regex@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||||
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
||||||
|
|
||||||
hey-listen@^1.0.5, hey-listen@^1.0.8:
|
|
||||||
version "1.0.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
|
|
||||||
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
|
|
||||||
|
|
||||||
highlight.js@^9.3.0:
|
highlight.js@^9.3.0:
|
||||||
version "9.15.6"
|
version "9.15.6"
|
||||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.6.tgz#72d4d8d779ec066af9a17cb14360c3def0aa57c4"
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.6.tgz#72d4d8d779ec066af9a17cb14360c3def0aa57c4"
|
||||||
|
@ -6894,17 +6850,17 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
|
||||||
yargs "^13.2.2"
|
yargs "^13.2.2"
|
||||||
zstd-codec "^0.1.1"
|
zstd-codec "^0.1.1"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#64383d57873ce59dea9df7216ee6cf52c4e95dc6:
|
lbry-redux@lbryio/lbry-redux#42bf926138872d14523be7191694309be4f37605:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/64383d57873ce59dea9df7216ee6cf52c4e95dc6"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/42bf926138872d14523be7191694309be4f37605"
|
||||||
dependencies:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
uuid "^3.3.2"
|
uuid "^3.3.2"
|
||||||
|
|
||||||
lbryinc@lbryio/lbryinc#d250096a6fc5df16be4f82812ecce28d6e558b6e:
|
lbryinc@lbryio/lbryinc#67bb3e215be3f13605c5e3f9f2b0e2fb880724cf:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/d250096a6fc5df16be4f82812ecce28d6e558b6e"
|
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/67bb3e215be3f13605c5e3f9f2b0e2fb880724cf"
|
||||||
dependencies:
|
dependencies:
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
||||||
|
@ -8715,32 +8671,6 @@ please-upgrade-node@^3.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver-compare "^1.0.0"
|
semver-compare "^1.0.0"
|
||||||
|
|
||||||
popmotion-pose@^3.4.0:
|
|
||||||
version "3.4.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/popmotion-pose/-/popmotion-pose-3.4.8.tgz#a50d7d2e91014405402f23400b08994fd148b5ce"
|
|
||||||
integrity sha512-/dkEhDiTYkbLb15dkrU3Okh58KU5I8z3f18V7kciN/cJmSc8ZD8tWgOc8U9yJf3lUHnf/va5PMCX4/4RnVeUiQ==
|
|
||||||
dependencies:
|
|
||||||
"@popmotion/easing" "^1.0.1"
|
|
||||||
hey-listen "^1.0.5"
|
|
||||||
popmotion "^8.6.2"
|
|
||||||
pose-core "^2.1.0"
|
|
||||||
style-value-types "^3.0.6"
|
|
||||||
ts-essentials "^1.0.3"
|
|
||||||
tslib "^1.9.1"
|
|
||||||
|
|
||||||
popmotion@^8.6.2:
|
|
||||||
version "8.6.10"
|
|
||||||
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-8.6.10.tgz#1133105f234fb617285bd254e473276153279219"
|
|
||||||
integrity sha512-pXGkj1Iy61sgwJvdKabHVP+5IeyCN2DdjdpAAVGhRhT31VYoLlKVOI6+SI747CecuyBI6zxSdZDNrrjdrldKuQ==
|
|
||||||
dependencies:
|
|
||||||
"@popmotion/easing" "^1.0.1"
|
|
||||||
"@popmotion/popcorn" "^0.4.0"
|
|
||||||
framesync "^4.0.0"
|
|
||||||
hey-listen "^1.0.5"
|
|
||||||
style-value-types "^3.1.4"
|
|
||||||
stylefire "^4.1.3"
|
|
||||||
tslib "^1.9.1"
|
|
||||||
|
|
||||||
portfinder@^1.0.20:
|
portfinder@^1.0.20:
|
||||||
version "1.0.20"
|
version "1.0.20"
|
||||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.20.tgz#bea68632e54b2e13ab7b0c4775e9b41bf270e44a"
|
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.20.tgz#bea68632e54b2e13ab7b0c4775e9b41bf270e44a"
|
||||||
|
@ -8750,16 +8680,6 @@ portfinder@^1.0.20:
|
||||||
debug "^2.2.0"
|
debug "^2.2.0"
|
||||||
mkdirp "0.5.x"
|
mkdirp "0.5.x"
|
||||||
|
|
||||||
pose-core@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/pose-core/-/pose-core-2.1.0.tgz#653c85a9a06f924611b909b4a2180ce102bbb258"
|
|
||||||
integrity sha512-36mVAnIgbM6jfyRug8EqqFbazHUAk9dxwVRpX61FlVw3amI/j7AFegtVU56N0Dht2aYDJIhgYPUYraT1CzjHDw==
|
|
||||||
dependencies:
|
|
||||||
"@types/invariant" "^2.2.29"
|
|
||||||
"@types/node" "^10.0.5"
|
|
||||||
hey-listen "^1.0.5"
|
|
||||||
tslib "^1.9.1"
|
|
||||||
|
|
||||||
posix-character-classes@^0.1.0:
|
posix-character-classes@^0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||||
|
@ -9805,16 +9725,6 @@ react-paginate@^5.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
prop-types "^15.6.1"
|
prop-types "^15.6.1"
|
||||||
|
|
||||||
react-pose@^4.0.5:
|
|
||||||
version "4.0.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-pose/-/react-pose-4.0.8.tgz#91bdfafde60e4096e7878a35dcc77715bed68f24"
|
|
||||||
integrity sha512-WN/583nKJZkKmKg5ha+eErOGWF9GV6A5EngC7WHQX5b910X9rTlOlxzdKlUy/dDcsTRMZEtHV0Sy2gLPYsVQCQ==
|
|
||||||
dependencies:
|
|
||||||
"@emotion/is-prop-valid" "^0.7.3"
|
|
||||||
hey-listen "^1.0.5"
|
|
||||||
popmotion-pose "^3.4.0"
|
|
||||||
tslib "^1.9.1"
|
|
||||||
|
|
||||||
react-redux@^6.0.1:
|
react-redux@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-6.0.1.tgz#0d423e2c1cb10ada87293d47e7de7c329623ba4d"
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-6.0.1.tgz#0d423e2c1cb10ada87293d47e7de7c329623ba4d"
|
||||||
|
@ -11330,23 +11240,6 @@ style-loader@^0.23.1:
|
||||||
loader-utils "^1.1.0"
|
loader-utils "^1.1.0"
|
||||||
schema-utils "^1.0.0"
|
schema-utils "^1.0.0"
|
||||||
|
|
||||||
style-value-types@^3.0.6, style-value-types@^3.1.4:
|
|
||||||
version "3.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.1.4.tgz#8c0959d26405eb0cbad40097b496220d41c22169"
|
|
||||||
integrity sha512-jHxRZWQpx6imY+QIveHTZwGOJWJqX3Cmt6Yk1zCGeQjk4noEsX+lfvFJUmRPpZL3VTrfGrHtjVWTcvcHx/OFhQ==
|
|
||||||
dependencies:
|
|
||||||
hey-listen "^1.0.8"
|
|
||||||
|
|
||||||
stylefire@^4.1.3:
|
|
||||||
version "4.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/stylefire/-/stylefire-4.1.4.tgz#42de066da75762bf086de33856a45ac95f7d66ce"
|
|
||||||
integrity sha512-bp9nNTTFHdIQp/4szBuF2z85rMAq5oySeAHdpNgPTcVlXDrwsi1FjjOLug/4+yx1p8eMFFGrkAex7b5/M95ivg==
|
|
||||||
dependencies:
|
|
||||||
"@popmotion/popcorn" "^0.4.0"
|
|
||||||
framesync "^4.0.0"
|
|
||||||
hey-listen "^1.0.8"
|
|
||||||
style-value-types "^3.1.4"
|
|
||||||
|
|
||||||
stylehacks@^4.0.0:
|
stylehacks@^4.0.0:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
|
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
|
||||||
|
@ -11781,12 +11674,7 @@ tryer@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
||||||
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
|
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
|
||||||
|
|
||||||
ts-essentials@^1.0.3:
|
tslib@^1.9.0:
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-1.0.4.tgz#ce3b5dade5f5d97cf69889c11bf7d2da8555b15a"
|
|
||||||
integrity sha512-q3N1xS4vZpRouhYHDPwO0bDW3EZ6SK9CrrDHxi/D6BPReSjpVgWIOpLS2o0gSBZm+7q/wyKp6RVM1AeeW7uyfQ==
|
|
||||||
|
|
||||||
tslib@^1.9.0, tslib@^1.9.1:
|
|
||||||
version "1.9.3"
|
version "1.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||||
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
||||||
|
|
Loading…
Reference in a new issue