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"],
|
||||
"new-cap": 0,
|
||||
"no-console": 1,
|
||||
"no-control-regex": 0,
|
||||
"no-multi-spaces": 0,
|
||||
"no-redeclare": 0,
|
||||
"no-return-await": 0,
|
||||
|
@ -38,11 +39,14 @@
|
|||
"react/jsx-indent": 0,
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"space-before-function-paren": ["error", {
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"space-before-function-paren": [
|
||||
"error",
|
||||
{
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}
|
||||
],
|
||||
"standard/object-curly-even-spacing": 0,
|
||||
"standard/no-callback-literal": 0,
|
||||
"react/display-name": 0,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
[libs]
|
||||
./flow-typed
|
||||
node_modules/lbry-redux/flow-typed/
|
||||
node_modules/lbryinc/flow-typed/
|
||||
|
||||
[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",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#64383d57873ce59dea9df7216ee6cf52c4e95dc6",
|
||||
"lbryinc": "lbryio/lbryinc#d250096a6fc5df16be4f82812ecce28d6e558b6e",
|
||||
"lbry-redux": "lbryio/lbry-redux#42bf926138872d14523be7191694309be4f37605",
|
||||
"lbryinc": "lbryio/lbryinc#67bb3e215be3f13605c5e3f9f2b0e2fb880724cf",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
"lodash-es": "^4.17.14",
|
||||
|
@ -157,7 +157,6 @@
|
|||
"react-hot-loader": "^4.11.1",
|
||||
"react-modal": "^3.1.7",
|
||||
"react-paginate": "^5.2.1",
|
||||
"react-pose": "^4.0.5",
|
||||
"react-redux": "^6.0.1",
|
||||
"react-router": "^5.0.0",
|
||||
"react-router-dom": "^5.0.0",
|
||||
|
|
|
@ -17,7 +17,7 @@ export default appState => {
|
|||
});
|
||||
|
||||
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,
|
||||
minHeight: 600,
|
||||
autoHideMenuBar: true,
|
||||
|
|
|
@ -31,8 +31,7 @@ let showingAutoUpdateCloseAlert = false;
|
|||
// object is garbage collected.
|
||||
let rendererWindow;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let tray;
|
||||
let tray; // eslint-disable-line
|
||||
let daemon;
|
||||
|
||||
const appState = {};
|
||||
|
@ -47,7 +46,6 @@ if (isDev) {
|
|||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line space-before-function-paren
|
||||
const startDaemon = async () => {
|
||||
let isDaemonRunning = false;
|
||||
|
||||
|
@ -114,7 +112,6 @@ if (!gotSingleInstanceLock) {
|
|||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line space-before-function-paren
|
||||
app.on('ready', async () => {
|
||||
await startDaemon();
|
||||
startSandbox();
|
||||
|
@ -317,6 +314,12 @@ ipcMain.on('set-auth-token', (event, token) => {
|
|||
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 => {
|
||||
keytar.getPassword('LBRY', 'wallet_password').then(password => {
|
||||
event.sender.send('get-password-response', password ? password.toString() : null);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
const { parseURI } = require('lbry-redux');
|
||||
// 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 { readFileSync } = require('fs');
|
||||
const express = require('express');
|
||||
|
@ -72,7 +69,7 @@ app.get('*', async (req, res) => {
|
|||
let html = readFileSync(path.join(__dirname, '/index.html'), 'utf8');
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { connect } from 'react-redux';
|
||||
import { doFetchTransactions } from 'lbry-redux';
|
||||
import { selectUser, doRewardList, doFetchRewardedContent, doFetchAccessToken, selectAccessToken } from 'lbryinc';
|
||||
import { doFetchTransactions, doFetchChannelListMine } from 'lbry-redux';
|
||||
import { makeSelectClientSetting, selectThemePath } from 'redux/selectors/settings';
|
||||
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
|
||||
import { doDownloadUpgradeRequested } from 'redux/actions/app';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { doDownloadUpgradeRequested, doSignIn } from 'redux/actions/app';
|
||||
import App from './view';
|
||||
|
||||
const select = state => ({
|
||||
|
@ -23,6 +23,8 @@ const perform = dispatch => ({
|
|||
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
|
||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
||||
onSignedIn: () => dispatch(doSignIn()),
|
||||
});
|
||||
|
||||
export default hot(
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import * as ICONS from 'constants/icons';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import analytics from 'analytics';
|
||||
import { Lbry, buildURI, parseURI } from 'lbry-redux';
|
||||
import { buildURI, parseURI } from 'lbry-redux';
|
||||
import Router from 'component/router/index';
|
||||
import ModalRouter from 'modal/modalRouter';
|
||||
import ReactModal from 'react-modal';
|
||||
|
@ -12,6 +12,7 @@ import Yrbl from 'component/yrbl';
|
|||
import FileViewer from 'component/fileViewer';
|
||||
import { withRouter } from 'react-router';
|
||||
import usePrevious from 'util/use-previous';
|
||||
import Button from 'component/button';
|
||||
|
||||
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
|
||||
|
||||
|
@ -20,7 +21,6 @@ type Props = {
|
|||
pageTitle: ?string,
|
||||
language: string,
|
||||
theme: string,
|
||||
accessToken: ?string,
|
||||
user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean },
|
||||
location: { pathname: string, hash: string },
|
||||
fetchRewards: () => void,
|
||||
|
@ -30,6 +30,8 @@ type Props = {
|
|||
autoUpdateDownloaded: boolean,
|
||||
isUpgradeAvailable: boolean,
|
||||
requestDownloadUpgrade: () => void,
|
||||
fetchChannelListMine: () => void,
|
||||
onSignedIn: () => void,
|
||||
};
|
||||
|
||||
function App(props: Props) {
|
||||
|
@ -40,10 +42,11 @@ function App(props: Props) {
|
|||
fetchTransactions,
|
||||
user,
|
||||
fetchAccessToken,
|
||||
accessToken,
|
||||
requestDownloadUpgrade,
|
||||
autoUpdateDownloaded,
|
||||
isUpgradeAvailable,
|
||||
fetchChannelListMine,
|
||||
onSignedIn,
|
||||
} = props;
|
||||
const appRef = useRef();
|
||||
const isEnhancedLayout = useKonamiListener();
|
||||
|
@ -70,8 +73,9 @@ function App(props: Props) {
|
|||
// @if TARGET='app'
|
||||
fetchRewards();
|
||||
fetchTransactions();
|
||||
fetchChannelListMine(); // This needs to be done for web too...
|
||||
// @endif
|
||||
}, [fetchRewards, fetchRewardedContent, fetchTransactions, fetchAccessToken]);
|
||||
}, [fetchRewards, fetchRewardedContent, fetchTransactions, fetchAccessToken, fetchChannelListMine]);
|
||||
|
||||
useEffect(() => {
|
||||
// $FlowFixMe
|
||||
|
@ -87,24 +91,27 @@ function App(props: Props) {
|
|||
useEffect(() => {
|
||||
// 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
|
||||
if (previousHasVerifiedEmail !== undefined && hasVerifiedEmail) {
|
||||
if (previousHasVerifiedEmail === false && hasVerifiedEmail) {
|
||||
analytics.emailVerifiedEvent();
|
||||
}
|
||||
}, [previousHasVerifiedEmail, hasVerifiedEmail]);
|
||||
}, [previousHasVerifiedEmail, hasVerifiedEmail, onSignedIn]);
|
||||
|
||||
useEffect(() => {
|
||||
if (previousRewardApproved !== undefined && isRewardApproved) {
|
||||
if (previousRewardApproved === false && isRewardApproved) {
|
||||
analytics.rewardEligibleEvent();
|
||||
}
|
||||
}, [previousRewardApproved, isRewardApproved]);
|
||||
|
||||
// @if TARGET='web'
|
||||
// Keep this at the end to ensure initial setup effects are run first
|
||||
useEffect(() => {
|
||||
if (hasVerifiedEmail && accessToken) {
|
||||
Lbry.setApiHeader('X-Lbry-Auth-Token', accessToken);
|
||||
if (!previousHasVerifiedEmail && hasVerifiedEmail) {
|
||||
onSignedIn();
|
||||
}
|
||||
}, [hasVerifiedEmail, accessToken]);
|
||||
// @endif
|
||||
}, [previousHasVerifiedEmail, hasVerifiedEmail, onSignedIn]);
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<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 Button from 'component/button';
|
||||
|
||||
|
@ -6,35 +7,33 @@ let scriptLoading = false;
|
|||
let scriptLoaded = false;
|
||||
let scriptDidError = false;
|
||||
|
||||
type Props = {
|
||||
disabled: boolean,
|
||||
label: ?string,
|
||||
email: string,
|
||||
// Flow does not like the way this stripe plugin works
|
||||
// Disabled because it was a huge pain
|
||||
// type Props = {
|
||||
// disabled: boolean,
|
||||
// label: ?string,
|
||||
// email: string,
|
||||
|
||||
// =====================================================
|
||||
// Required by stripe
|
||||
// see Stripe docs for more info:
|
||||
// https://stripe.com/docs/checkout#integration-custom
|
||||
// =====================================================
|
||||
// // =====================================================
|
||||
// // Required by stripe
|
||||
// // see Stripe docs for more info:
|
||||
// // https://stripe.com/docs/checkout#integration-custom
|
||||
// // =====================================================
|
||||
|
||||
// Your publishable key (test or live).
|
||||
// can't use "key" as a prop in react, so have to change the keyname
|
||||
stripeKey: string,
|
||||
// // Your publishable key (test or live).
|
||||
// // can't use "key" as a prop in react, so have to change the keyname
|
||||
// stripeKey: string,
|
||||
|
||||
// The callback to invoke when the Checkout process is complete.
|
||||
// function(token)
|
||||
// token is the token object created.
|
||||
// token.id can be used to create a charge or customer.
|
||||
// token.email contains the email address entered by the user.
|
||||
token: string,
|
||||
};
|
||||
// // The callback to invoke when the Checkout process is complete.
|
||||
// // function(token)
|
||||
// // token is the token object created.
|
||||
// // token.id can be used to create a charge or customer.
|
||||
// // token.email contains the email address entered by the user.
|
||||
// token: string,
|
||||
// };
|
||||
|
||||
type State = {
|
||||
open: boolean,
|
||||
};
|
||||
|
||||
class CardVerify extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
class CardVerify extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
open: false,
|
||||
|
@ -87,6 +86,7 @@ class CardVerify extends React.Component<Props, State> {
|
|||
|
||||
this.loadPromise.promise.then(this.onScriptLoaded).catch(this.onScriptError);
|
||||
|
||||
// $FlowFixMe
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@ class CardVerify extends React.Component<Props, State> {
|
|||
render() {
|
||||
return (
|
||||
<Button
|
||||
button="inverse"
|
||||
button="primary"
|
||||
label={this.props.label}
|
||||
disabled={this.props.disabled || this.state.open || this.hasPendingClick}
|
||||
onClick={this.onClick.bind(this)}
|
||||
|
@ -171,3 +171,5 @@ class CardVerify extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
export default CardVerify;
|
||||
/* eslint-enable no-undef */
|
||||
/* eslint-enable react/prop-types */
|
||||
|
|
|
@ -15,7 +15,6 @@ type Props = {
|
|||
uris: Array<string>,
|
||||
header: Node | boolean,
|
||||
headerAltControls: Node,
|
||||
injectedItem?: Node,
|
||||
loading: boolean,
|
||||
type: string,
|
||||
empty?: string,
|
||||
|
@ -33,7 +32,6 @@ export default function ClaimList(props: Props) {
|
|||
const {
|
||||
uris,
|
||||
headerAltControls,
|
||||
injectedItem,
|
||||
loading,
|
||||
persistedStorageKey,
|
||||
empty,
|
||||
|
@ -57,7 +55,7 @@ export default function ClaimList(props: Props) {
|
|||
|
||||
useEffect(() => {
|
||||
setScrollBottomCbMap({});
|
||||
}, [id]);
|
||||
}, [id, setScrollBottomCbMap]);
|
||||
|
||||
useEffect(() => {
|
||||
function handleScroll(e) {
|
||||
|
@ -112,8 +110,6 @@ export default function ClaimList(props: Props) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{injectedItem && <div>{injectedItem}</div>}
|
||||
|
||||
{urisLength > 0 && (
|
||||
<ul className="ul--no-style">
|
||||
{sortedUris.map((uri, index) => (
|
||||
|
@ -121,6 +117,7 @@ export default function ClaimList(props: Props) {
|
|||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{urisLength === 0 && !loading && <p className="main--empty empty">{empty || __('No results')}</p>}
|
||||
</section>
|
||||
);
|
||||
|
|
|
@ -32,13 +32,13 @@ type Props = {
|
|||
uris: Array<string>,
|
||||
subscribedChannels: Array<Subscription>,
|
||||
doClaimSearch: ({}) => void,
|
||||
injectedItem: any,
|
||||
tags: Array<string>,
|
||||
loading: boolean,
|
||||
personalView: boolean,
|
||||
doToggleTagFollow: string => void,
|
||||
meta?: Node,
|
||||
showNsfw: boolean,
|
||||
hideCustomization: boolean,
|
||||
history: { action: string, push: string => void, replace: string => void },
|
||||
location: { search: string, pathname: string },
|
||||
claimSearchByQuery: {
|
||||
|
@ -54,21 +54,21 @@ function ClaimListDiscover(props: Props) {
|
|||
tags,
|
||||
loading,
|
||||
personalView,
|
||||
injectedItem,
|
||||
meta,
|
||||
subscribedChannels,
|
||||
showNsfw,
|
||||
history,
|
||||
location,
|
||||
hiddenUris,
|
||||
hideCustomization,
|
||||
} = props;
|
||||
const didNavigateForward = history.action === 'PUSH';
|
||||
const [page, setPage] = useState(1);
|
||||
const { search } = location;
|
||||
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 timeSort = urlParams.get('time') || TIME_WEEK;
|
||||
const [page, setPage] = useState(1);
|
||||
const tagsInUrl = urlParams.get('t') || '';
|
||||
const options: {
|
||||
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
|
||||
// it's faster, but we will need to remove it if we start using total_pages
|
||||
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]) : [],
|
||||
not_channel_ids: hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : [],
|
||||
not_tags: !showNsfw ? MATURE_TAGS : [],
|
||||
|
@ -191,29 +191,33 @@ function ClaimListDiscover(props: Props) {
|
|||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
<span>{__('For')}</span>
|
||||
{!personalView && tags && tags.length ? (
|
||||
tags.map(tag => <Tag key={tag} name={tag} disabled />)
|
||||
) : (
|
||||
<FormField
|
||||
type="select"
|
||||
name="trending_overview"
|
||||
className="claim-list__dropdown"
|
||||
value={personalSort}
|
||||
onChange={e => {
|
||||
handlePersonalSort(e.target.value);
|
||||
}}
|
||||
>
|
||||
{SEARCH_FILTER_TYPES.map(type => (
|
||||
<option key={type} value={type}>
|
||||
{type === SEARCH_SORT_ALL
|
||||
? __('Everyone')
|
||||
: type === SEARCH_SORT_YOU
|
||||
? __('Tags You Follow')
|
||||
: __('Channels You Follow')}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
{!hideCustomization && (
|
||||
<Fragment>
|
||||
<span>{__('For')}</span>
|
||||
{!personalView && tags && tags.length ? (
|
||||
tags.map(tag => <Tag key={tag} name={tag} disabled />)
|
||||
) : (
|
||||
<FormField
|
||||
type="select"
|
||||
name="trending_overview"
|
||||
className="claim-list__dropdown"
|
||||
value={personalSort}
|
||||
onChange={e => {
|
||||
handlePersonalSort(e.target.value);
|
||||
}}
|
||||
>
|
||||
{SEARCH_FILTER_TYPES.map(type => (
|
||||
<option key={type} value={type}>
|
||||
{type === SEARCH_SORT_ALL
|
||||
? __('Everyone')
|
||||
: type === SEARCH_SORT_YOU
|
||||
? __('Tags You Follow')
|
||||
: __('Channels You Follow')}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
{typeSort === 'top' && (
|
||||
<FormField
|
||||
|
@ -242,7 +246,6 @@ function ClaimListDiscover(props: Props) {
|
|||
id={claimSearchCacheQuery}
|
||||
loading={loading}
|
||||
uris={uris}
|
||||
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}
|
||||
header={header}
|
||||
headerAltControls={meta}
|
||||
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>
|
||||
<fieldset-section>
|
||||
<label htmlFor={name}>{errorMessage ? <span className="error-text">{errorMessage}</span> : label}</label>
|
||||
{prefix && (
|
||||
<label className="form-field--inline-prefix" htmlFor={name}>
|
||||
{prefix}
|
||||
</label>
|
||||
)}
|
||||
{prefix && <label htmlFor={name}>{prefix}</label>}
|
||||
{inner}
|
||||
</fieldset-section>
|
||||
</React.Fragment>
|
||||
|
|
|
@ -11,6 +11,7 @@ export class Form extends React.PureComponent<Props> {
|
|||
const { children, onSubmit, ...otherProps } = this.props;
|
||||
return (
|
||||
<form
|
||||
noValidate
|
||||
className="form"
|
||||
onSubmit={event => {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -302,4 +302,9 @@ export const icons = {
|
|||
<line x1="21" y1="12" x2="9" y2="12" />
|
||||
</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,
|
||||
size?: number,
|
||||
className?: string,
|
||||
sectionIcon?: boolean,
|
||||
};
|
||||
|
||||
class IconComponent extends React.PureComponent<Props> {
|
||||
|
@ -49,11 +50,10 @@ class IconComponent extends React.PureComponent<Props> {
|
|||
};
|
||||
|
||||
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];
|
||||
|
||||
if (!Icon) {
|
||||
console.error('no icon found for ', icon);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,11 @@ class IconComponent extends React.PureComponent<Props> {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectBalance, formatCredits } from 'lbry-redux';
|
||||
import { selectUserEmail } from 'lbryinc';
|
||||
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doSignOut } from 'redux/actions/app';
|
||||
import Header from './view';
|
||||
|
||||
const select = state => ({
|
||||
|
@ -13,11 +14,12 @@ const select = state => ({
|
|||
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
|
||||
automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state),
|
||||
hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state),
|
||||
email: selectUserEmail(state),
|
||||
email: selectUserVerifiedEmail(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||
signOut: () => dispatch(doSignOut()),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -12,22 +12,6 @@ import Icon from 'component/common/icon';
|
|||
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
||||
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 = {
|
||||
autoUpdateDownloaded: boolean,
|
||||
balance: string,
|
||||
|
@ -41,6 +25,7 @@ type Props = {
|
|||
hideBalance: boolean,
|
||||
email: ?string,
|
||||
minimal: boolean,
|
||||
signOut: () => void,
|
||||
};
|
||||
|
||||
const Header = (props: Props) => {
|
||||
|
@ -53,11 +38,9 @@ const Header = (props: Props) => {
|
|||
hideBalance,
|
||||
email,
|
||||
minimal,
|
||||
signOut,
|
||||
} = props;
|
||||
const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable);
|
||||
const isAuthenticated = Boolean(email);
|
||||
// Auth is optional in the desktop app
|
||||
const showFullHeader = IS_WEB ? isAuthenticated : true;
|
||||
const authenticated = Boolean(email);
|
||||
|
||||
function handleThemeToggle() {
|
||||
if (automaticDarkModeEnabled) {
|
||||
|
@ -71,42 +54,14 @@ const Header = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
function signOut() {
|
||||
// Replace this with actual clearUser function
|
||||
window.store.dispatch({ type: 'USER_FETCH_FAILURE' });
|
||||
deleteAuthToken();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
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'),
|
||||
});
|
||||
function getWalletTitle() {
|
||||
return hideBalance ? (
|
||||
__('Wallet')
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{roundedBalance} <LbcSymbol />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -146,19 +101,17 @@ const Header = (props: Props) => {
|
|||
</div>
|
||||
|
||||
{!minimal ? (
|
||||
<div className="header__menu">
|
||||
{showFullHeader ? (
|
||||
<div className={classnames('header__menu', { 'header__menu--small': IS_WEB && !authenticated })}>
|
||||
{!IS_WEB || authenticated ? (
|
||||
<Fragment>
|
||||
<Menu>
|
||||
<MenuButton className="header__navigation-item menu__title">
|
||||
{roundedBalance} <LbcSymbol />
|
||||
</MenuButton>
|
||||
<MenuButton className="header__navigation-item menu__title">{getWalletTitle()}</MenuButton>
|
||||
<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} />
|
||||
{__('Wallet')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/rewards`)}>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
||||
<Icon aria-hidden icon={ICONS.FEATURED} />
|
||||
{__('Rewards')}
|
||||
</MenuItem>
|
||||
|
@ -169,19 +122,16 @@ const Header = (props: Props) => {
|
|||
<Icon size={18} icon={ICONS.ACCOUNT} />
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem
|
||||
className="menu__link"
|
||||
onSelect={() => history.push(isAuthenticated ? `/$/account` : `/$/auth/signup`)}
|
||||
>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.ACCOUNT}`)}>
|
||||
<Icon aria-hidden icon={ICONS.OVERVIEW} />
|
||||
{__('Overview')}
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISH}`)}>
|
||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||
{__('Publish')}
|
||||
</MenuItem>
|
||||
{isAuthenticated ? (
|
||||
{authenticated ? (
|
||||
<MenuItem className="menu__link" onSelect={signOut}>
|
||||
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
||||
{__('Sign Out')}
|
||||
|
@ -197,11 +147,11 @@ const Header = (props: Props) => {
|
|||
<Icon size={18} icon={ICONS.SETTINGS} />
|
||||
</MenuButton>
|
||||
<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} />
|
||||
{__('Settings')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/help`)}>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
|
||||
<Icon aria-hidden icon={ICONS.HELP} />
|
||||
{__('Help')}
|
||||
</MenuItem>
|
||||
|
@ -213,10 +163,7 @@ const Header = (props: Props) => {
|
|||
</Menu>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<span />
|
||||
<Button navigate={`/$/${PAGES.AUTH}/signin`} button="primary" label={__('Sign In')} />
|
||||
</Fragment>
|
||||
<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Sign In')} />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import RewardLink from 'component/rewardLink';
|
||||
import Yrbl from 'component/yrbl';
|
||||
import { rewards } from 'lbryinc';
|
||||
import Icon from 'component/common/icon';
|
||||
import * as ICONS from 'constants/icons';
|
||||
|
@ -20,22 +19,10 @@ class InviteList extends React.PureComponent<Props> {
|
|||
render() {
|
||||
const { invitees, referralReward } = this.props;
|
||||
|
||||
if (!invitees) {
|
||||
if (!invitees || !invitees.length) {
|
||||
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 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!."
|
||||
|
@ -60,10 +47,10 @@ class InviteList extends React.PureComponent<Props> {
|
|||
/>
|
||||
)}
|
||||
</h2>
|
||||
<p className="card__subtitle">{rewardHelp}</p>
|
||||
<p className="section__subtitle">{rewardHelp}</p>
|
||||
</div>
|
||||
|
||||
<table className="table table--invites">
|
||||
<table className="table section">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{__('Invitee Email')}</th>
|
||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||
import Button from 'component/button';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
import CopyableText from 'component/copyableText';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type FormProps = {
|
||||
inviteNew: string => void,
|
||||
|
@ -73,25 +74,28 @@ class InviteNew extends React.PureComponent<Props> {
|
|||
const { errorMessage, inviteNew, isPending, rewardAmount, referralLink } = this.props;
|
||||
|
||||
return (
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('Invite a Friend')}</h2>
|
||||
<p className="card__subtitle">{__('When your friends start using LBRY, the network gets stronger!')}</p>
|
||||
<Card
|
||||
title={__('Invite a Friend')}
|
||||
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
|
||||
errorMessage={errorMessage}
|
||||
inviteNew={inviteNew}
|
||||
isPending={isPending}
|
||||
rewardAmount={rewardAmount}
|
||||
/>
|
||||
<CopyableText label={__('Or share this link with your friends')} copyable={referralLink} />
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||
import Page from './view';
|
||||
|
||||
export default Page;
|
||||
const select = state => ({
|
||||
authenticated: Boolean(selectUserVerifiedEmail(state)),
|
||||
});
|
||||
|
||||
export default connect(select)(Page);
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
// @flow
|
||||
import type { Node } from 'react';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React, { Fragment } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import SideBar from 'component/sideBar';
|
||||
import Header from 'component/header';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
children: Node | Array<Node>,
|
||||
|
@ -13,35 +11,19 @@ type Props = {
|
|||
autoUpdateDownloaded: boolean,
|
||||
isUpgradeAvailable: boolean,
|
||||
fullscreen: boolean,
|
||||
doDownloadUpgradeRequested: () => void,
|
||||
authenticated: boolean,
|
||||
};
|
||||
|
||||
function Page(props: Props) {
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
fullscreen = false,
|
||||
autoUpdateDownloaded,
|
||||
isUpgradeAvailable,
|
||||
doDownloadUpgradeRequested,
|
||||
} = props;
|
||||
const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable);
|
||||
const { children, className, fullscreen = false, authenticated } = props;
|
||||
const obscureSideBar = IS_WEB ? !authenticated : false;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Header minimal={fullscreen} />
|
||||
<div className={classnames('main-wrapper__inner')}>
|
||||
{/* @if TARGET='app' */}
|
||||
{showUpgradeButton && (
|
||||
<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 />}
|
||||
<main className={classnames('main', className, { 'main--full-width': fullscreen })}>{children}</main>
|
||||
{!fullscreen && <SideBar obscureSideBar={obscureSideBar} />}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -32,7 +32,7 @@ const RewardLink = (props: Props) => {
|
|||
|
||||
return !reward ? null : (
|
||||
<Button
|
||||
button={button ? 'inverse' : 'link'}
|
||||
button={button ? 'primary' : 'link'}
|
||||
disabled={isPending}
|
||||
label={displayLabel}
|
||||
onClick={() => {
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
|||
import Button from 'component/button';
|
||||
import CreditAmount from 'component/common/credit-amount';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type Props = {
|
||||
unclaimedRewardAmount: number,
|
||||
|
@ -14,33 +15,35 @@ class RewardSummary extends React.Component<Props> {
|
|||
const { unclaimedRewardAmount, fetching } = this.props;
|
||||
const hasRewards = unclaimedRewardAmount > 0;
|
||||
return (
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('Rewards')}</h2>
|
||||
|
||||
<p className="card__subtitle">
|
||||
{fetching && __('You have...')}
|
||||
{!fetching && hasRewards ? (
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
credit_amount: <CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />,
|
||||
}}
|
||||
>
|
||||
You have %credit_amount% in unclaimed rewards.
|
||||
</I18nMessage>
|
||||
) : (
|
||||
__('You have no rewards available.')
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div className="card__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
navigate="/$/rewards"
|
||||
label={hasRewards ? __('Claim Rewards') : __('View Rewards')}
|
||||
/>
|
||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />.
|
||||
</div>
|
||||
</section>
|
||||
<Card
|
||||
title={__('Rewards')}
|
||||
subtitle={
|
||||
<React.Fragment>
|
||||
{fetching && __('You have...')}
|
||||
{!fetching && hasRewards ? (
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
credit_amount: <CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />,
|
||||
}}
|
||||
>
|
||||
You have %credit_amount% in unclaimed rewards.
|
||||
</I18nMessage>
|
||||
) : (
|
||||
__('You have no rewards available.')
|
||||
)}
|
||||
</React.Fragment>
|
||||
}
|
||||
actions={
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
navigate="/$/rewards"
|
||||
label={hasRewards ? __('Claim Rewards') : __('View Rewards')}
|
||||
/>
|
||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />.
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import React from 'react';
|
|||
import Icon from 'component/common/icon';
|
||||
import RewardLink from 'component/rewardLink';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import { rewards } from 'lbryinc';
|
||||
|
||||
type Props = {
|
||||
|
@ -25,27 +26,28 @@ const RewardTile = (props: Props) => {
|
|||
const claimed = !!reward.transaction_id;
|
||||
|
||||
return (
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{reward.reward_title}</h2>
|
||||
<p className="card__subtitle">{reward.reward_description}</p>
|
||||
|
||||
<div className="card__actions">
|
||||
{reward.reward_type === rewards.TYPE_GENERATED_CODE && (
|
||||
<Button button="inverse" onClick={openRewardCodeModal} label={__('Enter Code')} />
|
||||
)}
|
||||
{reward.reward_type === rewards.TYPE_REFERRAL && (
|
||||
<Button button="inverse" navigate="/$/invite" label={__('Go To Invites')} />
|
||||
)}
|
||||
{reward.reward_type !== rewards.TYPE_REFERRAL &&
|
||||
(claimed ? (
|
||||
<span>
|
||||
<Icon icon={ICONS.COMPLETED} /> {__('Reward claimed.')}
|
||||
</span>
|
||||
) : (
|
||||
<RewardLink button reward_type={reward.reward_type} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<Card
|
||||
title={reward.reward_title}
|
||||
subtitle={reward.reward_description}
|
||||
actions={
|
||||
<div className="card__actions">
|
||||
{reward.reward_type === rewards.TYPE_GENERATED_CODE && (
|
||||
<Button button="primary" onClick={openRewardCodeModal} label={__('Enter Code')} />
|
||||
)}
|
||||
{reward.reward_type === rewards.TYPE_REFERRAL && (
|
||||
<Button button="primary" navigate="/$/invite" label={__('Go To Invites')} />
|
||||
)}
|
||||
{reward.reward_type !== rewards.TYPE_REFERRAL &&
|
||||
(claimed ? (
|
||||
<span>
|
||||
<Icon icon={ICONS.COMPLETED} /> {__('Reward claimed.')}
|
||||
</span>
|
||||
) : (
|
||||
<RewardLink button reward_type={reward.reward_type} />
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||
import { selectScrollStartingPosition } from 'redux/selectors/app';
|
||||
import Router from './view';
|
||||
|
||||
const select = state => ({
|
||||
currentScroll: selectScrollStartingPosition(state),
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
});
|
||||
|
||||
export default connect(select)(Router);
|
||||
|
|
|
@ -13,12 +13,10 @@ import RewardsPage from 'page/rewards';
|
|||
import FileListDownloaded from 'page/fileListDownloaded';
|
||||
import FileListPublished from 'page/fileListPublished';
|
||||
import TransactionHistoryPage from 'page/transactionHistory';
|
||||
import AuthPage from 'page/auth';
|
||||
import InvitePage from 'page/invite';
|
||||
import SearchPage from 'page/search';
|
||||
import LibraryPage from 'page/library';
|
||||
import WalletPage from 'page/wallet';
|
||||
import NavigationHistory from 'page/navigationHistory';
|
||||
import TagsPage from 'page/tags';
|
||||
import FollowingPage from 'page/following';
|
||||
import ListBlockedPage from 'page/listBlocked';
|
||||
|
@ -30,9 +28,32 @@ if ('scrollRestoration' in history) {
|
|||
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 = {
|
||||
currentScroll: number,
|
||||
location: { pathname: string, search: string },
|
||||
isAuthenticated: boolean,
|
||||
};
|
||||
|
||||
function AppRouter(props: Props) {
|
||||
|
@ -46,25 +67,25 @@ function AppRouter(props: Props) {
|
|||
<Switch>
|
||||
<Route path="/" exact component={DiscoverPage} />
|
||||
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
|
||||
<Route path={`/$/${PAGES.AUTH}`} exact component={AuthPage} />
|
||||
<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.AUTH}`} exact component={SignInPage} />
|
||||
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
|
||||
<Route path={`/$/${PAGES.FOLLOWING}`} exact component={FollowingPage} />
|
||||
<Route path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
||||
<Route path={`/$/${PAGES.BLOCKED}`} exact component={ListBlockedPage} />
|
||||
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
||||
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
||||
|
||||
<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 */}
|
||||
<Route path="/:claimName" 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 { SEARCH_OPTIONS } from 'lbry-redux';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
import posed from 'react-pose';
|
||||
import Button from 'component/button';
|
||||
|
||||
const ExpandableOptions = posed.div({
|
||||
hide: { height: 0, opacity: 0 },
|
||||
show: { height: 380, opacity: 1 },
|
||||
});
|
||||
|
||||
type Props = {
|
||||
setSearchOption: (string, boolean | string | number) => void,
|
||||
options: {},
|
||||
|
@ -30,93 +24,91 @@ const SearchOptions = (props: Props) => {
|
|||
iconRight={expanded ? ICONS.UP : ICONS.DOWN}
|
||||
onClick={toggleSearchExpanded}
|
||||
/>
|
||||
<ExpandableOptions pose={expanded ? 'show' : 'hide'}>
|
||||
{expanded && (
|
||||
<Form className="search__options">
|
||||
<fieldset>
|
||||
<legend className="search__legend--1">{__('Search For')}</legend>
|
||||
{[
|
||||
{
|
||||
option: SEARCH_OPTIONS.INCLUDE_FILES,
|
||||
label: __('Files'),
|
||||
},
|
||||
{
|
||||
option: SEARCH_OPTIONS.INCLUDE_CHANNELS,
|
||||
label: __('Channels'),
|
||||
},
|
||||
{
|
||||
option: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
|
||||
label: __('Everything'),
|
||||
},
|
||||
].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>
|
||||
{expanded && (
|
||||
<Form className="search__options">
|
||||
<fieldset>
|
||||
<legend className="search__legend--1">{__('Search For')}</legend>
|
||||
{[
|
||||
{
|
||||
option: SEARCH_OPTIONS.INCLUDE_FILES,
|
||||
label: __('Files'),
|
||||
},
|
||||
{
|
||||
option: SEARCH_OPTIONS.INCLUDE_CHANNELS,
|
||||
label: __('Channels'),
|
||||
},
|
||||
{
|
||||
option: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
|
||||
label: __('Everything'),
|
||||
},
|
||||
].map(({ option, label }) => (
|
||||
<FormField
|
||||
type="select"
|
||||
name="result-count"
|
||||
value={resultCount}
|
||||
onChange={e => setSearchOption(SEARCH_OPTIONS.RESULT_COUNT, e.target.value)}
|
||||
key={option}
|
||||
name={option}
|
||||
type="radio"
|
||||
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>
|
||||
)}
|
||||
</ExpandableOptions>
|
||||
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
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
|||
|
||||
type Props = {
|
||||
channel: string, // currently selected channel
|
||||
channels: Array<{ name: string }>,
|
||||
channels: ?Array<{ name: string }>,
|
||||
balance: number,
|
||||
onChannelChange: string => void,
|
||||
createChannel: (string, number) => Promise<any>,
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import Tag from 'component/tag';
|
||||
import StickyBox from 'react-sticky-box/dist/esnext';
|
||||
import 'css-doodle';
|
||||
|
||||
type Props = {
|
||||
subscriptions: Array<Subscription>,
|
||||
followedTags: Array<Tag>,
|
||||
email: ?string,
|
||||
obscureSideBar: boolean,
|
||||
};
|
||||
|
||||
function SideBar(props: Props) {
|
||||
const { subscriptions, followedTags, email } = props;
|
||||
const showSideBar = IS_WEB ? Boolean(email) : true;
|
||||
|
||||
const { subscriptions, followedTags, obscureSideBar } = props;
|
||||
function buildLink(path, label, icon, guide) {
|
||||
return {
|
||||
navigate: path ? `$/${path}` : '/',
|
||||
|
@ -25,68 +25,67 @@ function SideBar(props: Props) {
|
|||
};
|
||||
}
|
||||
|
||||
return (
|
||||
return obscureSideBar ? (
|
||||
<StickyBox offsetTop={100} offsetBottom={20}>
|
||||
{showSideBar ? (
|
||||
<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>
|
||||
<div className="card navigation--placeholder">
|
||||
<div className="wrap">
|
||||
<h2>LBRY</h2>
|
||||
|
||||
{email ? (
|
||||
<Fragment>
|
||||
<p>{__('The best decentralized content platform on the web.')}</p>
|
||||
<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
|
||||
navigate={`/$/${PAGES.FOLLOWING}`}
|
||||
label={__('Customize')}
|
||||
icon={ICONS.EDIT}
|
||||
navigate={uri}
|
||||
label={channelName}
|
||||
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
|
||||
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" />
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</StickyBox>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,26 +1,30 @@
|
|||
// @flow
|
||||
import React, { useState } from 'react';
|
||||
import { useTransition, animated } from 'react-spring';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
import Tag from 'component/tag';
|
||||
|
||||
const unfollowedTagsAnimation = {
|
||||
from: { opacity: 0 },
|
||||
enter: { opacity: 1, maxWidth: 200 },
|
||||
leave: { opacity: 0, maxWidth: 0 },
|
||||
};
|
||||
|
||||
type Props = {
|
||||
tagsPasssedIn: Array<Tag>,
|
||||
unfollowedTags: Array<Tag>,
|
||||
followedTags: Array<Tag>,
|
||||
doToggleTagFollow: string => void,
|
||||
doAddTag: string => void,
|
||||
onSelect?: Tag => void,
|
||||
suggestMature?: boolean,
|
||||
onRemove: Tag => void,
|
||||
};
|
||||
|
||||
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('');
|
||||
|
||||
let tags = unfollowedTags.slice();
|
||||
|
@ -39,8 +43,6 @@ export default function TagSelect(props: Props) {
|
|||
suggestedTags.push('mature');
|
||||
}
|
||||
|
||||
const suggestedTransitions = useTransition(suggestedTags, tag => tag, unfollowedTagsAnimation);
|
||||
|
||||
function onChange(e) {
|
||||
setNewTag(e.target.value);
|
||||
}
|
||||
|
@ -76,24 +78,37 @@ export default function TagSelect(props: Props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormField
|
||||
label={__('Find New Tags')}
|
||||
onChange={onChange}
|
||||
placeholder={__('Search for more tags')}
|
||||
type="text"
|
||||
value={newTag}
|
||||
/>
|
||||
<React.Fragment>
|
||||
<Form className="tags__input-wrapper" onSubmit={handleSubmit}>
|
||||
<ul className="tags--remove">
|
||||
{tagsPasssedIn.map(tag => (
|
||||
<Tag
|
||||
key={tag.name}
|
||||
name={tag.name}
|
||||
type="remove"
|
||||
onClick={() => {
|
||||
onRemove(tag);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<li>
|
||||
<FormField
|
||||
autoFocus
|
||||
className="tag__input"
|
||||
onChange={onChange}
|
||||
placeholder={__('Follow more tags')}
|
||||
type="text"
|
||||
value={newTag}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</Form>
|
||||
<ul className="tags">
|
||||
{suggestedTransitions.map(({ item, key, props }) => (
|
||||
<animated.li key={key} style={props}>
|
||||
<Tag name={item} type="add" onClick={() => handleTagClick(item)} />
|
||||
</animated.li>
|
||||
{suggestedTags.map(tag => (
|
||||
<Tag key={tag} name={tag} type="add" onClick={() => handleTagClick(tag)} />
|
||||
))}
|
||||
{!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>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import Button from 'component/button';
|
||||
import Tag from 'component/tag';
|
||||
import TagsSearch from 'component/tagsSearch';
|
||||
import usePersistedState from 'util/use-persisted-state';
|
||||
import { useTransition, animated } from 'react-spring';
|
||||
import analytics from 'analytics';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type Props = {
|
||||
showClose?: boolean,
|
||||
|
@ -17,17 +17,9 @@ type Props = {
|
|||
// The default component is for following tags
|
||||
title?: string | boolean,
|
||||
help?: string,
|
||||
empty?: string,
|
||||
tagsChosen?: Array<Tag>,
|
||||
onSelect?: 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) {
|
||||
|
@ -37,16 +29,14 @@ export default function TagSelect(props: Props) {
|
|||
doToggleTagFollow = null,
|
||||
title,
|
||||
help,
|
||||
empty,
|
||||
tagsChosen,
|
||||
onSelect,
|
||||
onRemove,
|
||||
suggestMature,
|
||||
className,
|
||||
} = props;
|
||||
const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false);
|
||||
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');
|
||||
|
||||
function handleClose() {
|
||||
|
@ -65,40 +55,43 @@ export default function TagSelect(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (tagCount === 0) {
|
||||
setHasClosed(false);
|
||||
}
|
||||
}, [tagCount, setHasClosed]);
|
||||
|
||||
return (
|
||||
((showClose && !hasClosed) || !showClose) && (
|
||||
<div className={className}>
|
||||
{title !== false && (
|
||||
<h2 className="card__title">
|
||||
<Card
|
||||
icon={ICONS.TAG}
|
||||
title={
|
||||
<React.Fragment>
|
||||
{title}
|
||||
{showClose && !hasClosed && <Button button="close" icon={ICONS.REMOVE} onClick={handleClose} />}
|
||||
</h2>
|
||||
)}
|
||||
|
||||
<ul className="tags--remove">
|
||||
{transitions.map(({ item, props, key }) => (
|
||||
<animated.li key={key} style={props}>
|
||||
<Tag
|
||||
name={item.name}
|
||||
type="remove"
|
||||
onClick={() => {
|
||||
handleTagClick(item);
|
||||
}}
|
||||
{showClose && tagsToDisplay.length > 0 && !hasClosed && (
|
||||
<Button button="close" icon={ICONS.REMOVE} onClick={handleClose} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
}
|
||||
body={
|
||||
<React.Fragment>
|
||||
<section className="section">
|
||||
<TagsSearch
|
||||
onRemove={handleTagClick}
|
||||
onSelect={onSelect}
|
||||
suggestMature={suggestMature && !hasMatureTag}
|
||||
tagsPasssedIn={tagsToDisplay}
|
||||
/>
|
||||
</animated.li>
|
||||
))}
|
||||
{!transitions.length && (
|
||||
<div className="empty">{empty || __("You aren't following any tags, try searching for one.")}</div>
|
||||
)}
|
||||
</ul>
|
||||
<TagsSearch onSelect={onSelect} suggestMature={suggestMature && !hasMatureTag} />
|
||||
{help !== false && (
|
||||
<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>
|
||||
{help !== false && (
|
||||
<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>
|
||||
)}
|
||||
</section>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,10 +14,8 @@ class TransactionListRecent extends React.PureComponent<Props> {
|
|||
componentDidMount() {
|
||||
const { fetchMyClaims, fetchTransactions } = this.props;
|
||||
|
||||
// @if TARGET='app'
|
||||
fetchMyClaims();
|
||||
fetchTransactions();
|
||||
// @endif
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import type { Node } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import Button from 'component/button';
|
||||
import { FormField } from 'component/common/form';
|
||||
import UserEmailNew from 'component/userEmailNew';
|
||||
import UserEmailVerify from 'component/userEmailVerify';
|
||||
import UserEmailResetButton from 'component/userEmailResetButton';
|
||||
import UserSignOutButton from 'component/userSignOutButton';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type Props = {
|
||||
cancelButton: Node,
|
||||
|
@ -34,44 +34,34 @@ function UserEmail(props: Props) {
|
|||
}, [accessToken, fetchAccessToken]);
|
||||
|
||||
return (
|
||||
<section className="card card--section">
|
||||
{!email && <UserEmailNew />}
|
||||
{user && email && !isVerified && <UserEmailVerify />}
|
||||
{email && isVerified && (
|
||||
<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>
|
||||
<Card
|
||||
title={__('Email')}
|
||||
subtitle={__(
|
||||
'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.'
|
||||
)}
|
||||
</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,
|
||||
};
|
||||
|
||||
// "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) {
|
||||
const { errorMessage, isPending, addUserEmail } = props;
|
||||
const [newEmail, setEmail] = useState('');
|
||||
const [sync, setSync] = useState(false);
|
||||
const valid = newEmail.match(EMAIL_REGEX);
|
||||
|
||||
function handleSubmit() {
|
||||
addUserEmail(newEmail);
|
||||
|
@ -26,26 +30,26 @@ function UserEmailNew(props: Props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormField
|
||||
type="email"
|
||||
id="sign_up_email"
|
||||
label={__('Email')}
|
||||
value={newEmail}
|
||||
error={errorMessage}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
/>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
id="sign_up_sync"
|
||||
label={__('Sync my bidnezz on this device')}
|
||||
helper={__('Maybe some additional text with this field')}
|
||||
checked={sync}
|
||||
onChange={() => setSync(!sync)}
|
||||
/>
|
||||
|
||||
<Button button="primary" type="submit" label={__('Continue')} disabled={isPending} />
|
||||
</Form>
|
||||
<div>
|
||||
<h1 className="section__title--large">{__('Welcome To LBRY')}</h1>
|
||||
<p className="section__subtitle">{__('Create a new account or sign in.')}</p>
|
||||
<Form onSubmit={handleSubmit} className="section__body">
|
||||
<FormField
|
||||
autoFocus
|
||||
className="form-field--short"
|
||||
placeholder={__('hotstuff_96@hotmail.com')}
|
||||
type="email"
|
||||
id="sign_up_email"
|
||||
label={__('Email')}
|
||||
value={newEmail}
|
||||
error={errorMessage}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
/>
|
||||
<div className="card__actions">
|
||||
<Button button="primary" type="submit" label={__('Continue')} disabled={!newEmail || !valid || isPending} />
|
||||
</div>
|
||||
</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
|
||||
import * as React from 'react';
|
||||
import Button from 'component/button';
|
||||
import UserEmailResetButton from 'component/userEmailResetButton';
|
||||
import UserSignOutButton from 'component/userSignOutButton';
|
||||
|
||||
type Props = {
|
||||
email: string,
|
||||
|
@ -52,18 +52,19 @@ class UserEmailVerify extends React.PureComponent<Props> {
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<p className="card__subtitle">
|
||||
{__('An email was sent to')} {email}.{' '}
|
||||
{__('Follow the link and you will be good to go. This will update automatically.')}
|
||||
<h1 className="section__title--large">{__('Confirm Your Email')}</h1>
|
||||
|
||||
<p className="section__subtitle">
|
||||
{__('An email was sent to')} {email}. {__('Follow the link to sign in. This will update automatically.')}
|
||||
</p>
|
||||
|
||||
<div className="card__actions">
|
||||
<div className="section__body section__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('Resend Verification Email')}
|
||||
onClick={this.handleResendVerificationEmail}
|
||||
/>
|
||||
<UserEmailResetButton />
|
||||
<UserSignOutButton label={__('Start Over')} />
|
||||
</div>
|
||||
|
||||
<p className="help">
|
||||
|
|
|
@ -1,28 +1,21 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUser, selectEmailToVerify, rewards as REWARD_TYPES, doClaimRewardType } from 'lbryinc';
|
||||
import { doCreateChannel, selectCreatingChannel, selectMyChannelClaims } from 'lbry-redux';
|
||||
import UserSignUp from './view';
|
||||
import { selectUser, selectEmailToVerify } from 'lbryinc';
|
||||
import { doCreateChannel, selectCreatingChannel, selectMyChannelClaims, selectCreateChannelError } from 'lbry-redux';
|
||||
import UserFirstChannel from './view';
|
||||
|
||||
const select = state => ({
|
||||
email: selectEmailToVerify(state),
|
||||
user: selectUser(state),
|
||||
creatingChannel: selectCreatingChannel(state),
|
||||
channels: selectMyChannelClaims(state),
|
||||
creatingChannel: selectCreatingChannel(state),
|
||||
createChannelError: selectCreateChannelError(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
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(
|
||||
select,
|
||||
perform
|
||||
)(UserSignUp);
|
||||
)(UserFirstChannel);
|
||||
|
|
|
@ -4,23 +4,25 @@ import { isNameValid } from 'lbry-redux';
|
|||
import Button from 'component/button';
|
||||
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 = {
|
||||
createChannel: (string, number) => void,
|
||||
claimReward: (() => void) => void,
|
||||
creatingChannel: boolean,
|
||||
createChannelError: string,
|
||||
claimingReward: boolean,
|
||||
user: User,
|
||||
};
|
||||
|
||||
function UserFirstChannel(props: Props) {
|
||||
const { createChannel, creatingChannel, claimReward } = props;
|
||||
const [channel, setChannel] = useState('');
|
||||
const [nameError, setNameError] = useState();
|
||||
const { createChannel, creatingChannel, claimingReward, user, createChannelError } = props;
|
||||
const { primary_email: primaryEmail } = user;
|
||||
const initialChannel = primaryEmail.split('@')[0];
|
||||
const [channel, setChannel] = useState(initialChannel);
|
||||
const [nameError, setNameError] = useState(undefined);
|
||||
|
||||
function handleCreateChannel() {
|
||||
claimReward(() => {
|
||||
createChannel(`@${channel}`, DEFAULT_BID_FOR_FIRST_CHANNEL);
|
||||
});
|
||||
createChannel(`@${channel}`, DEFAULT_BID_FOR_FIRST_CHANNEL);
|
||||
}
|
||||
|
||||
function handleChannelChange(e) {
|
||||
|
@ -35,29 +37,43 @@ function UserFirstChannel(props: Props) {
|
|||
|
||||
return (
|
||||
<Form onSubmit={handleCreateChannel}>
|
||||
<h1 className="card__title--large">{__('Choose Your Channel Name')}</h1>
|
||||
<p className="card__subtitle">
|
||||
{__("Normally this would cost LBRY credits, but we're hooking you up for your first one.")}
|
||||
</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}
|
||||
/>
|
||||
<h1 className="section__title--large">{__('Create A Channel')}</h1>
|
||||
<div className="section__subtitle">
|
||||
<p>{__('A channel is your identity on the LBRY network.')}</p>
|
||||
<p>{__('You can have more than one or change this later.')}</p>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,27 +1,39 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectEmailToVerify,
|
||||
doUserResendVerificationEmail,
|
||||
doUserCheckEmailVerified,
|
||||
selectUser,
|
||||
doFetchAccessToken,
|
||||
selectAccessToken,
|
||||
makeSelectIsRewardClaimPending,
|
||||
selectClaimedRewards,
|
||||
rewards as REWARD_TYPES,
|
||||
doClaimRewardType,
|
||||
} from 'lbryinc';
|
||||
import UserSignUp from './view';
|
||||
import { selectMyChannelClaims, selectBalance, selectFetchingMyChannels } from 'lbry-redux';
|
||||
import UserSignIn from './view';
|
||||
|
||||
const select = state => ({
|
||||
email: selectEmailToVerify(state),
|
||||
user: selectUser(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 => ({
|
||||
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
|
||||
checkEmailVerified: () => dispatch(doUserCheckEmailVerified()),
|
||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||
claimReward: () =>
|
||||
dispatch(
|
||||
doClaimRewardType(REWARD_TYPES.TYPE_CONFIRM_EMAIL, {
|
||||
notifyError: false,
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(UserSignUp);
|
||||
)(UserSignIn);
|
||||
|
|
|
@ -1,32 +1,101 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import UserEmailNew from 'component/userEmailNew';
|
||||
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 = {
|
||||
user: ?User,
|
||||
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) {
|
||||
const { email, user } = props;
|
||||
const verifiedEmail = user && email && user.has_verified_email;
|
||||
function useFetched(fetching) {
|
||||
const wasFetching = usePrevious(fetching);
|
||||
const [fetched, setFetched] = React.useState(false);
|
||||
|
||||
function getTitle() {
|
||||
if (!email) {
|
||||
return __('Get Rockin');
|
||||
} else if (email && !verifiedEmail) {
|
||||
return __('We Sent You An Email');
|
||||
React.useEffect(() => {
|
||||
if (wasFetching && !fetching) {
|
||||
setFetched(true);
|
||||
}
|
||||
}, [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 (
|
||||
<section>
|
||||
<h1 className="card__title--large">{getTitle()}</h1>
|
||||
{!email && <UserEmailNew />}
|
||||
{email && !verifiedEmail && <UserEmailVerify />}
|
||||
{hasVerifiedEmail && !rewardsApproved ? (
|
||||
<UserVerify />
|
||||
) : (
|
||||
<div className="main--contained">
|
||||
{!email && !hasVerifiedEmail && <UserEmailNew />}
|
||||
{email && !hasVerifiedEmail && <UserEmailVerify />}
|
||||
{hasVerifiedEmail && balance && balance > 0 && channelCount === 0 && <UserFirstChannel />}
|
||||
</div>
|
||||
)}
|
||||
</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
|
||||
import * as React from 'react';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React, { Fragment } from 'react';
|
||||
import Button from 'component/button';
|
||||
import CardVerify from 'component/cardVerify';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type Props = {
|
||||
errorMessage: ?string,
|
||||
|
@ -26,91 +28,87 @@ class UserVerify extends React.PureComponent<Props> {
|
|||
const { errorMessage, isPending, verifyPhone } = this.props;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<section className="card card--section">
|
||||
<h1 className="card__title">{__('Final Human Proof')}</h1>
|
||||
<p className="card__subtitle">
|
||||
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">
|
||||
<section className="section--large">
|
||||
<h1 className="section__title--large">{__('Extra Verification Needed')}</h1>
|
||||
<p>
|
||||
{__(
|
||||
'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."
|
||||
)}{' '}
|
||||
{__(
|
||||
'This process will likely involve providing proof of a stable and established online or real-life identity.'
|
||||
<Button navigate="/" button="link" label={__('Skip')} />.
|
||||
</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">
|
||||
<Button href="https://chat.lbry.com" button="inverse" label={__('Join LBRY Chat')} />
|
||||
<div className="section__divider">
|
||||
<hr />
|
||||
<p>{__('OR')}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('Or, Skip It Entirely')}</h2>
|
||||
<p className="card__subtitle">
|
||||
{__('You can continue without this step, but you will not be eligible to earn rewards.')}
|
||||
</p>
|
||||
<Card
|
||||
icon={ICONS.WALLET}
|
||||
title={__('Proof via Credit')}
|
||||
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.'
|
||||
)}
|
||||
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">
|
||||
<Button navigate="/" button="primary" label={__('Skip Rewards')} />
|
||||
<div className="section__divider">
|
||||
<hr />
|
||||
<p>{__('OR')}</p>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||
import Button from 'component/button';
|
||||
import CopyableText from 'component/copyableText';
|
||||
import QRCode from 'component/common/qr-code';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type Props = {
|
||||
checkAddressIsMine: string => void,
|
||||
|
@ -46,29 +47,31 @@ class WalletAddress extends React.PureComponent<Props, State> {
|
|||
const { showQR } = this.state;
|
||||
|
||||
return (
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('Receive Credits')}</h2>
|
||||
<p className="card__subtitle">
|
||||
{__(
|
||||
'Use this address to receive LBC. You can generate a new address at any time, and any previous addresses will continue to work.'
|
||||
)}
|
||||
</p>
|
||||
<CopyableText label={__('Your Address')} copyable={receiveAddress} snackMessage={__('Address copied.')} />
|
||||
<Card
|
||||
title={__('Receive Credits')}
|
||||
subtitle={__(
|
||||
'Use this address to receive LBC. You can generate a new address at any time, and any previous addresses will continue to work.'
|
||||
)}
|
||||
actions={
|
||||
<React.Fragment>
|
||||
<CopyableText label={__('Your Address')} copyable={receiveAddress} snackMessage={__('Address copied.')} />
|
||||
|
||||
<div className="card__actions">
|
||||
{!IS_WEB && (
|
||||
<Button
|
||||
button="inverse"
|
||||
label={__('Get New Address')}
|
||||
onClick={getNewAddress}
|
||||
disabled={gettingNewAddress}
|
||||
/>
|
||||
)}
|
||||
<Button button="link" label={showQR ? __('Hide QR code') : __('Show QR code')} onClick={this.toggleQR} />
|
||||
</div>
|
||||
<div className="card__actions">
|
||||
{!IS_WEB && (
|
||||
<Button
|
||||
button="inverse"
|
||||
label={__('Get New Address')}
|
||||
onClick={getNewAddress}
|
||||
disabled={gettingNewAddress}
|
||||
/>
|
||||
)}
|
||||
<Button button="link" label={showQR ? __('Hide QR code') : __('Show QR code')} onClick={this.toggleQR} />
|
||||
</div>
|
||||
|
||||
{showQR && <QRCode value={receiveAddress} paddingTop />}
|
||||
</section>
|
||||
{showQR && <QRCode value={receiveAddress} paddingTop />}
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doWalletStatus,
|
||||
|
@ -27,7 +26,6 @@ import {
|
|||
selectUser,
|
||||
} from 'lbryinc';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
|
||||
const select = state => ({
|
||||
walletEncryptSucceeded: selectWalletEncryptSucceeded(state),
|
||||
|
@ -36,7 +34,6 @@ const select = state => ({
|
|||
walletEncrypted: selectWalletIsEncrypted(state),
|
||||
walletHasTransactions: selectHasTransactions(state),
|
||||
user: selectUser(state),
|
||||
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
|
||||
hasSyncedWallet: selectHasSyncedWallet(state),
|
||||
getSyncIsPending: selectGetSyncIsPending(state),
|
||||
setSyncIsPending: selectSetSyncIsPending(state),
|
||||
|
|
|
@ -1,404 +1,405 @@
|
|||
// @flow
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Form, FormField, Submit } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import UserEmail from 'component/userEmail';
|
||||
import * as ICONS from 'constants/icons';
|
||||
export default () => null;
|
||||
// // @flow
|
||||
// import React, { useState } from 'react';
|
||||
// import { Form, FormField, Submit } from 'component/common/form';
|
||||
// import Button from 'component/button';
|
||||
// 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 = {
|
||||
// wallet statuses
|
||||
walletEncryptSucceeded: boolean,
|
||||
walletEncryptPending: boolean,
|
||||
walletDecryptSucceeded: boolean,
|
||||
walletDecryptPending: boolean,
|
||||
updateWalletStatus: boolean,
|
||||
walletEncrypted: boolean,
|
||||
// wallet methods
|
||||
encryptWallet: (?string) => void,
|
||||
decryptWallet: (?string) => void,
|
||||
updateWalletStatus: () => void,
|
||||
// housekeeping
|
||||
setPasswordSaved: () => void,
|
||||
syncEnabled: boolean,
|
||||
setClientSetting: (string, boolean | string) => void,
|
||||
isPasswordSaved: boolean,
|
||||
// data
|
||||
user: any,
|
||||
// sync statuses
|
||||
hasSyncedWallet: boolean,
|
||||
getSyncIsPending?: boolean,
|
||||
syncApplyErrorMessage?: string,
|
||||
hashChanged: boolean,
|
||||
// sync data
|
||||
syncData: string | null,
|
||||
syncHash: string | null,
|
||||
// sync methods
|
||||
syncApply: (string | null, string | null, string) => void,
|
||||
checkSync: () => void,
|
||||
setDefaultAccount: () => void,
|
||||
hasTransactions: boolean,
|
||||
};
|
||||
// type Props = {
|
||||
// // wallet statuses
|
||||
// walletEncryptSucceeded: boolean,
|
||||
// walletEncryptPending: boolean,
|
||||
// walletDecryptSucceeded: boolean,
|
||||
// walletDecryptPending: boolean,
|
||||
// updateWalletStatus: boolean,
|
||||
// walletEncrypted: boolean,
|
||||
// // wallet methods
|
||||
// encryptWallet: (?string) => void,
|
||||
// decryptWallet: (?string) => void,
|
||||
// updateWalletStatus: () => void,
|
||||
// // housekeeping
|
||||
// setPasswordSaved: () => void,
|
||||
// syncEnabled: boolean,
|
||||
// setClientSetting: (string, boolean | string) => void,
|
||||
// isPasswordSaved: boolean,
|
||||
// // data
|
||||
// user: any,
|
||||
// // sync statuses
|
||||
// hasSyncedWallet: boolean,
|
||||
// getSyncIsPending?: boolean,
|
||||
// syncApplyErrorMessage?: string,
|
||||
// hashChanged: boolean,
|
||||
// // sync data
|
||||
// syncData: string | null,
|
||||
// syncHash: string | null,
|
||||
// // sync methods
|
||||
// syncApply: (string | null, string | null, string) => void,
|
||||
// checkSync: () => void,
|
||||
// setDefaultAccount: () => void,
|
||||
// hasTransactions: boolean,
|
||||
// };
|
||||
|
||||
type State = {
|
||||
newPassword: string,
|
||||
newPasswordConfirm: string,
|
||||
passwordMatch: boolean,
|
||||
understandConfirmed: boolean,
|
||||
understandError: boolean,
|
||||
submitted: boolean,
|
||||
failMessage: ?string,
|
||||
rememberPassword: boolean,
|
||||
showEmailReg: boolean,
|
||||
failed: boolean,
|
||||
enableSync: boolean,
|
||||
encryptWallet: boolean,
|
||||
obscurePassword: boolean,
|
||||
advancedMode: boolean,
|
||||
showPasswordFields: boolean,
|
||||
};
|
||||
// type State = {
|
||||
// newPassword: string,
|
||||
// newPasswordConfirm: string,
|
||||
// passwordMatch: boolean,
|
||||
// understandConfirmed: boolean,
|
||||
// understandError: boolean,
|
||||
// submitted: boolean,
|
||||
// failMessage: ?string,
|
||||
// rememberPassword: boolean,
|
||||
// showEmailReg: boolean,
|
||||
// failed: boolean,
|
||||
// enableSync: boolean,
|
||||
// encryptWallet: boolean,
|
||||
// obscurePassword: boolean,
|
||||
// advancedMode: boolean,
|
||||
// showPasswordFields: boolean,
|
||||
// };
|
||||
|
||||
function WalletSecurityAndSync(props: Props) {
|
||||
const {
|
||||
// walletEncryptSucceeded,
|
||||
// walletEncryptPending,
|
||||
// walletDecryptSucceeded,
|
||||
// walletDecryptPending,
|
||||
// updateWalletStatus,
|
||||
walletEncrypted,
|
||||
encryptWallet,
|
||||
decryptWallet,
|
||||
syncEnabled,
|
||||
user,
|
||||
hasSyncedWallet,
|
||||
getSyncIsPending,
|
||||
syncApplyErrorMessage,
|
||||
hashChanged,
|
||||
syncData,
|
||||
syncHash,
|
||||
syncApply,
|
||||
checkSync,
|
||||
hasTransactions,
|
||||
setDefaultAccount,
|
||||
} = props;
|
||||
// function WalletSecurityAndSync(props: Props) {
|
||||
// const {
|
||||
// // walletEncryptSucceeded,
|
||||
// // walletEncryptPending,
|
||||
// // walletDecryptSucceeded,
|
||||
// // walletDecryptPending,
|
||||
// // updateWalletStatus,
|
||||
// walletEncrypted,
|
||||
// encryptWallet,
|
||||
// decryptWallet,
|
||||
// syncEnabled,
|
||||
// user,
|
||||
// hasSyncedWallet,
|
||||
// getSyncIsPending,
|
||||
// syncApplyErrorMessage,
|
||||
// hashChanged,
|
||||
// syncData,
|
||||
// syncHash,
|
||||
// syncApply,
|
||||
// checkSync,
|
||||
// hasTransactions,
|
||||
// // setDefaultAccount,
|
||||
// } = props;
|
||||
|
||||
const defaultComponentState: State = {
|
||||
newPassword: '',
|
||||
newPasswordConfirm: '',
|
||||
passwordMatch: false,
|
||||
understandConfirmed: false,
|
||||
understandError: false,
|
||||
submitted: false, // Prior actions could be marked complete
|
||||
failMessage: undefined,
|
||||
rememberPassword: false,
|
||||
showEmailReg: false,
|
||||
failed: false,
|
||||
enableSync: syncEnabled,
|
||||
encryptWallet: walletEncrypted,
|
||||
obscurePassword: true,
|
||||
advancedMode: false,
|
||||
showPasswordFields: false,
|
||||
};
|
||||
const [componentState, setComponentState] = useState<State>(defaultComponentState);
|
||||
// const defaultComponentState: State = {
|
||||
// newPassword: '',
|
||||
// newPasswordConfirm: '',
|
||||
// passwordMatch: false,
|
||||
// understandConfirmed: false,
|
||||
// understandError: false,
|
||||
// submitted: false, // Prior actions could be marked complete
|
||||
// failMessage: undefined,
|
||||
// rememberPassword: false,
|
||||
// showEmailReg: false,
|
||||
// failed: false,
|
||||
// enableSync: syncEnabled,
|
||||
// encryptWallet: walletEncrypted,
|
||||
// obscurePassword: true,
|
||||
// advancedMode: false,
|
||||
// showPasswordFields: false,
|
||||
// };
|
||||
// const [componentState, setComponentState] = useState<State>(defaultComponentState);
|
||||
|
||||
const safeToSync = !hasTransactions || !hashChanged;
|
||||
// const safeToSync = !hasTransactions || !hashChanged;
|
||||
|
||||
// on mount
|
||||
useEffect(() => {
|
||||
checkSync();
|
||||
getSavedPassword().then(p => {
|
||||
if (p) {
|
||||
setComponentState({
|
||||
...componentState,
|
||||
newPassword: p,
|
||||
newPasswordConfirm: p,
|
||||
showPasswordFields: true,
|
||||
rememberPassword: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
// // on mount
|
||||
// // useEffect(() => {
|
||||
// // checkSync();
|
||||
// // getSavedPassword().then(p => {
|
||||
// // if (p) {
|
||||
// // setComponentState({
|
||||
// // ...componentState,
|
||||
// // newPassword: p,
|
||||
// // newPasswordConfirm: p,
|
||||
// // showPasswordFields: true,
|
||||
// // rememberPassword: true,
|
||||
// // });
|
||||
// // }
|
||||
// // });
|
||||
// // }, []);
|
||||
|
||||
useEffect(() => {
|
||||
setComponentState({
|
||||
...componentState,
|
||||
passwordMatch: componentState.newPassword === componentState.newPasswordConfirm,
|
||||
});
|
||||
}, [componentState.newPassword, componentState.newPasswordConfirm]);
|
||||
// // useEffect(() => {
|
||||
// // setComponentState({
|
||||
// // ...componentState,
|
||||
// // passwordMatch: componentState.newPassword === componentState.newPasswordConfirm,
|
||||
// // });
|
||||
// // }, [componentState.newPassword, componentState.newPasswordConfirm]);
|
||||
|
||||
const isEmailVerified = user && user.primary_email && user.has_verified_email;
|
||||
// const syncDisabledMessage = 'You cannot sync without an email';
|
||||
// const isEmailVerified = user && user.primary_email && user.has_verified_email;
|
||||
// // const syncDisabledMessage = 'You cannot sync without an email';
|
||||
|
||||
function onChangeNewPassword(event: SyntheticInputEvent<>) {
|
||||
setComponentState({ ...componentState, newPassword: event.target.value || '' });
|
||||
}
|
||||
// function onChangeNewPassword(event: SyntheticInputEvent<>) {
|
||||
// setComponentState({ ...componentState, newPassword: event.target.value || '' });
|
||||
// }
|
||||
|
||||
function onChangeRememberPassword(event: SyntheticInputEvent<>) {
|
||||
if (componentState.rememberPassword) {
|
||||
deleteSavedPassword();
|
||||
}
|
||||
setComponentState({ ...componentState, rememberPassword: event.target.checked });
|
||||
}
|
||||
// function onChangeRememberPassword(event: SyntheticInputEvent<>) {
|
||||
// if (componentState.rememberPassword) {
|
||||
// deleteAuthToken();
|
||||
// }
|
||||
// setComponentState({ ...componentState, rememberPassword: event.target.checked });
|
||||
// }
|
||||
|
||||
function onChangeNewPasswordConfirm(event: SyntheticInputEvent<>) {
|
||||
setComponentState({ ...componentState, newPasswordConfirm: event.target.value || '' });
|
||||
}
|
||||
// function onChangeNewPasswordConfirm(event: SyntheticInputEvent<>) {
|
||||
// setComponentState({ ...componentState, newPasswordConfirm: event.target.value || '' });
|
||||
// }
|
||||
|
||||
function onChangeUnderstandConfirm(event: SyntheticInputEvent<>) {
|
||||
setComponentState({ ...componentState, understandConfirmed: /^.?i understand.?$/i.test(event.target.value) });
|
||||
}
|
||||
// function onChangeUnderstandConfirm(event: SyntheticInputEvent<>) {
|
||||
// setComponentState({ ...componentState, understandConfirmed: /^.?i understand.?$/i.test(event.target.value) });
|
||||
// }
|
||||
|
||||
function onChangeSync(event: SyntheticInputEvent<>) {
|
||||
if (componentState.enableSync) {
|
||||
setComponentState({ ...componentState, enableSync: false, newPassword: '', newPasswordConfirm: '' });
|
||||
setComponentState({ ...componentState, enableSync: false, newPassword: '', newPasswordConfirm: '' });
|
||||
}
|
||||
if (!(walletEncrypted || syncApplyErrorMessage || componentState.advancedMode)) {
|
||||
easyApply();
|
||||
} else {
|
||||
setComponentState({ ...componentState, enableSync: true });
|
||||
}
|
||||
}
|
||||
// function onChangeSync(event: SyntheticInputEvent<>) {
|
||||
// if (componentState.enableSync) {
|
||||
// setComponentState({ ...componentState, enableSync: false, newPassword: '', newPasswordConfirm: '' });
|
||||
// setComponentState({ ...componentState, enableSync: false, newPassword: '', newPasswordConfirm: '' });
|
||||
// }
|
||||
// if (!(walletEncrypted || syncApplyErrorMessage || componentState.advancedMode)) {
|
||||
// easyApply();
|
||||
// } else {
|
||||
// setComponentState({ ...componentState, enableSync: true });
|
||||
// }
|
||||
// }
|
||||
|
||||
function onChangeEncrypt(event: SyntheticInputEvent<>) {
|
||||
setComponentState({ ...componentState, encryptWallet: event.target.checked });
|
||||
}
|
||||
// function onChangeEncrypt(event: SyntheticInputEvent<>) {
|
||||
// setComponentState({ ...componentState, encryptWallet: event.target.checked });
|
||||
// }
|
||||
|
||||
async function easyApply() {
|
||||
return new Promise((resolve, reject) => {
|
||||
return syncApply(syncHash, syncData, componentState.newPassword);
|
||||
})
|
||||
.then(() => {
|
||||
setComponentState({ ...componentState, enableSync: event.target.checked });
|
||||
})
|
||||
.catch();
|
||||
}
|
||||
// async function easyApply() {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// return syncApply(syncHash, syncData, componentState.newPassword);
|
||||
// })
|
||||
// .then(() => {
|
||||
// setComponentState({ ...componentState, enableSync: event.target.checked });
|
||||
// })
|
||||
// .catch();
|
||||
// }
|
||||
|
||||
async function apply() {
|
||||
setComponentState({ ...componentState, failed: false });
|
||||
// async function apply() {
|
||||
// setComponentState({ ...componentState, failed: false });
|
||||
|
||||
await checkSync();
|
||||
// await checkSync();
|
||||
|
||||
if (componentState.enableSync) {
|
||||
await syncApply(syncHash, syncData, componentState.newPassword);
|
||||
if (syncApplyErrorMessage) {
|
||||
setComponentState({ ...componentState, failed: true });
|
||||
}
|
||||
}
|
||||
// if (componentState.enableSync) {
|
||||
// await syncApply(syncHash, syncData, componentState.newPassword);
|
||||
// if (syncApplyErrorMessage) {
|
||||
// setComponentState({ ...componentState, failed: true });
|
||||
// }
|
||||
// }
|
||||
|
||||
if (walletEncrypted) {
|
||||
await decryptWallet();
|
||||
}
|
||||
// if (walletEncrypted) {
|
||||
// await decryptWallet();
|
||||
// }
|
||||
|
||||
if (componentState.encryptWallet && !componentState.failed) {
|
||||
await encryptWallet(componentState.newPassword)
|
||||
.then(() => {})
|
||||
.catch(() => {
|
||||
setComponentState({ ...componentState, failed: false });
|
||||
});
|
||||
}
|
||||
// if (componentState.encryptWallet && !componentState.failed) {
|
||||
// await encryptWallet(componentState.newPassword)
|
||||
// .then(() => {})
|
||||
// .catch(() => {
|
||||
// setComponentState({ ...componentState, failed: false });
|
||||
// });
|
||||
// }
|
||||
|
||||
if (componentState.rememberPassword && !componentState.failed) {
|
||||
setSavedPassword(componentState.newPassword);
|
||||
}
|
||||
}
|
||||
// if (componentState.rememberPassword && !componentState.failed) {
|
||||
// setSavedPassword(componentState.newPassword);
|
||||
// }
|
||||
// }
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('Wallet Sync and Security')}</h2>
|
||||
{!isEmailVerified && (
|
||||
<React.Fragment>
|
||||
<p className="card__subtitle">
|
||||
{__(`It looks like we don't have your email.`)}{' '}
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Verify your email')}
|
||||
onClick={() => setComponentState({ ...componentState, showEmailReg: !componentState.showEmailReg })}
|
||||
/>{' '}
|
||||
{__(`and then come back here.`)}
|
||||
</p>
|
||||
{componentState.showEmailReg && <UserEmail />}
|
||||
</React.Fragment>
|
||||
)}
|
||||
<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.'
|
||||
)}{' '}
|
||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />.
|
||||
</p>
|
||||
{/* Errors and status */}
|
||||
{!componentState.advancedMode && (
|
||||
<React.Fragment>
|
||||
<p className="card__subtitle">
|
||||
{__(`Easy Mode: Sync and go with default security! Don't trust your roommate?`)}{' '}
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Advanced Mode')}
|
||||
onClick={() => setComponentState({ ...componentState, advancedMode: !componentState.advancedMode })}
|
||||
/>
|
||||
.
|
||||
</p>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{componentState.advancedMode && (
|
||||
<React.Fragment>
|
||||
<p className="card__subtitle">
|
||||
{__('Advanced Mode: Enter a password that matches your other devices LBRY password.')}{' '}
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Easy Mode')}
|
||||
onClick={() => setComponentState({ ...componentState, advancedMode: !componentState.advancedMode })}
|
||||
/>
|
||||
.
|
||||
</p>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{syncApplyErrorMessage && <div className="card__subtitle--status">{__(syncApplyErrorMessage)}</div>}
|
||||
// return (
|
||||
// <React.Fragment>
|
||||
// <section className="card card--section">
|
||||
// <h2 className="card__title">{__('Wallet Sync and Security')}</h2>
|
||||
// {!isEmailVerified && (
|
||||
// <React.Fragment>
|
||||
// <p className="card__subtitle">
|
||||
// {__(`It looks like we don't have your email.`)}{' '}
|
||||
// <Button
|
||||
// button="link"
|
||||
// label={__('Verify your email')}
|
||||
// onClick={() => setComponentState({ ...componentState, showEmailReg: !componentState.showEmailReg })}
|
||||
// />{' '}
|
||||
// {__(`and then come back here.`)}
|
||||
// </p>
|
||||
// {componentState.showEmailReg && <UserEmail />}
|
||||
// </React.Fragment>
|
||||
// )}
|
||||
// <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.'
|
||||
// )}{' '}
|
||||
// <Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />.
|
||||
// </p>
|
||||
// {/* Errors and status */}
|
||||
// {!componentState.advancedMode && (
|
||||
// <React.Fragment>
|
||||
// <p className="card__subtitle">
|
||||
// {__(`Easy Mode: Sync and go with default security! Don't trust your roommate?`)}{' '}
|
||||
// <Button
|
||||
// button="link"
|
||||
// label={__('Advanced Mode')}
|
||||
// onClick={() => setComponentState({ ...componentState, advancedMode: !componentState.advancedMode })}
|
||||
// />
|
||||
// .
|
||||
// </p>
|
||||
// </React.Fragment>
|
||||
// )}
|
||||
// {componentState.advancedMode && (
|
||||
// <React.Fragment>
|
||||
// <p className="card__subtitle">
|
||||
// {__('Advanced Mode: Enter a password that matches your other devices LBRY password.')}{' '}
|
||||
// <Button
|
||||
// button="link"
|
||||
// label={__('Easy Mode')}
|
||||
// onClick={() => setComponentState({ ...componentState, advancedMode: !componentState.advancedMode })}
|
||||
// />
|
||||
// .
|
||||
// </p>
|
||||
// </React.Fragment>
|
||||
// )}
|
||||
// {syncApplyErrorMessage && <div className="card__subtitle--status">{__(syncApplyErrorMessage)}</div>}
|
||||
|
||||
<Form onSubmit={() => apply()}>
|
||||
{componentState.advancedMode && (
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="sync_enabled"
|
||||
checked={componentState.enableSync}
|
||||
disabled={!isEmailVerified || !safeToSync}
|
||||
prefix={<span className="badge badge--alert">ALPHA</span>}
|
||||
onChange={event => onChangeSync(event)}
|
||||
label={
|
||||
<React.Fragment>
|
||||
{__('Enable Sync')} <Button button="link" label={__('(?)')} href="https://lbry.com/privacypolicy" />{' '}
|
||||
<span className="badge badge--alert">ALPHA</span>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!componentState.advancedMode && (
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('Sync my wallet')}
|
||||
onClick={() => syncApply(syncHash, syncData, componentState.newPassword)}
|
||||
/>
|
||||
)}
|
||||
{(walletEncrypted ||
|
||||
syncApplyErrorMessage ||
|
||||
componentState.advancedMode ||
|
||||
componentState.showPasswordFields) && (
|
||||
<React.Fragment>
|
||||
<FormField
|
||||
autoFocus
|
||||
inputButton={
|
||||
<Button
|
||||
icon={componentState.obscurePassword ? ICONS.EYE : ICONS.EYE_OFF}
|
||||
button={'primary'}
|
||||
onClick={() =>
|
||||
setComponentState({ ...componentState, obscurePassword: !componentState.obscurePassword })
|
||||
}
|
||||
/>
|
||||
}
|
||||
label={__('Password')}
|
||||
placeholder={__('Shh...')}
|
||||
type={componentState.obscurePassword ? 'password' : 'text'}
|
||||
name="wallet-new-password"
|
||||
onChange={event => onChangeNewPassword(event)}
|
||||
value={componentState.newPassword}
|
||||
/>
|
||||
<FormField
|
||||
error={componentState.passwordMatch === false ? 'No match' : false}
|
||||
label={__('Same Password')}
|
||||
placeholder={__('Your eyes only')}
|
||||
type="password"
|
||||
name="wallet-new-password-confirm"
|
||||
onChange={event => onChangeNewPasswordConfirm(event)}
|
||||
value={componentState.newPasswordConfirm}
|
||||
/>
|
||||
<FormField
|
||||
label={__('Remember Password')}
|
||||
type="checkbox"
|
||||
name="wallet-remember-password"
|
||||
onChange={event => onChangeRememberPassword(event)}
|
||||
checked={componentState.rememberPassword}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
// <Form onSubmit={() => apply()}>
|
||||
// {componentState.advancedMode && (
|
||||
// <FormField
|
||||
// type="checkbox"
|
||||
// name="sync_enabled"
|
||||
// checked={componentState.enableSync}
|
||||
// disabled={!isEmailVerified || !safeToSync}
|
||||
// prefix={<span className="badge badge--alert">ALPHA</span>}
|
||||
// onChange={event => onChangeSync(event)}
|
||||
// label={
|
||||
// <React.Fragment>
|
||||
// {__('Enable Sync')} <Button button="link" label={__('(?)')} href="https://lbry.com/privacypolicy" />{' '}
|
||||
// <span className="badge badge--alert">ALPHA</span>
|
||||
// </React.Fragment>
|
||||
// }
|
||||
// />
|
||||
// )}
|
||||
// {!componentState.advancedMode && (
|
||||
// <Button
|
||||
// button="primary"
|
||||
// label={__('Sync my wallet')}
|
||||
// onClick={() => syncApply(syncHash, syncData, componentState.newPassword)}
|
||||
// />
|
||||
// )}
|
||||
// {(walletEncrypted ||
|
||||
// syncApplyErrorMessage ||
|
||||
// componentState.advancedMode ||
|
||||
// componentState.showPasswordFields) && (
|
||||
// <React.Fragment>
|
||||
// <FormField
|
||||
// autoFocus
|
||||
// inputButton={
|
||||
// <Button
|
||||
// icon={componentState.obscurePassword ? ICONS.EYE : ICONS.EYE_OFF}
|
||||
// button={'primary'}
|
||||
// onClick={() =>
|
||||
// setComponentState({ ...componentState, obscurePassword: !componentState.obscurePassword })
|
||||
// }
|
||||
// />
|
||||
// }
|
||||
// label={__('Password')}
|
||||
// placeholder={__('Shh...')}
|
||||
// type={componentState.obscurePassword ? 'password' : 'text'}
|
||||
// name="wallet-new-password"
|
||||
// onChange={event => onChangeNewPassword(event)}
|
||||
// value={componentState.newPassword}
|
||||
// />
|
||||
// <FormField
|
||||
// error={componentState.passwordMatch === false ? 'No match' : false}
|
||||
// label={__('Same Password')}
|
||||
// placeholder={__('Your eyes only')}
|
||||
// type="password"
|
||||
// name="wallet-new-password-confirm"
|
||||
// onChange={event => onChangeNewPasswordConfirm(event)}
|
||||
// value={componentState.newPasswordConfirm}
|
||||
// />
|
||||
// <FormField
|
||||
// label={__('Remember Password')}
|
||||
// type="checkbox"
|
||||
// name="wallet-remember-password"
|
||||
// onChange={event => onChangeRememberPassword(event)}
|
||||
// checked={componentState.rememberPassword}
|
||||
// />
|
||||
// </React.Fragment>
|
||||
// )}
|
||||
|
||||
{/* Confirmation */}
|
||||
// {/* Confirmation */}
|
||||
|
||||
{(walletEncrypted || componentState.advancedMode) && (
|
||||
<React.Fragment>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="encrypt_enabled"
|
||||
checked={componentState.encryptWallet}
|
||||
disabled={false}
|
||||
onChange={event => onChangeEncrypt(event)}
|
||||
label={__('Encrypt Wallet')}
|
||||
/>
|
||||
<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.'
|
||||
)}
|
||||
</div>
|
||||
<FormField
|
||||
error={componentState.understandError === true ? 'You must enter "I understand"' : false}
|
||||
label={__('Enter "I understand"')}
|
||||
placeholder={__('I understand')}
|
||||
type="text"
|
||||
name="wallet-understand"
|
||||
onChange={event => onChangeUnderstandConfirm(event)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
// {(walletEncrypted || componentState.advancedMode) && (
|
||||
// <React.Fragment>
|
||||
// <FormField
|
||||
// type="checkbox"
|
||||
// name="encrypt_enabled"
|
||||
// checked={componentState.encryptWallet}
|
||||
// disabled={false}
|
||||
// onChange={event => onChangeEncrypt(event)}
|
||||
// label={__('Encrypt Wallet')}
|
||||
// />
|
||||
// <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.'
|
||||
// )}
|
||||
// </div>
|
||||
// <FormField
|
||||
// error={componentState.understandError === true ? 'You must enter "I understand"' : false}
|
||||
// label={__('Enter "I understand"')}
|
||||
// placeholder={__('I understand')}
|
||||
// type="text"
|
||||
// name="wallet-understand"
|
||||
// onChange={event => onChangeUnderstandConfirm(event)}
|
||||
// />
|
||||
// </React.Fragment>
|
||||
// )}
|
||||
|
||||
{componentState.failMessage && <div className="error-text">{__(componentState.failMessage)}</div>}
|
||||
{(walletEncrypted || componentState.advancedMode || syncApplyErrorMessage) && (
|
||||
<Submit
|
||||
disabled={!componentState.passwordMatch || (!componentState.enableSync && !componentState.encryptWallet)}
|
||||
label={componentState.failMessage ? __('Encrypting Wallet') : __('Apply')}
|
||||
/>
|
||||
)}
|
||||
</Form>
|
||||
</section>
|
||||
// {componentState.failMessage && <div className="error-text">{__(componentState.failMessage)}</div>}
|
||||
// {(walletEncrypted || componentState.advancedMode || syncApplyErrorMessage) && (
|
||||
// <Submit
|
||||
// disabled={!componentState.passwordMatch || (!componentState.enableSync && !componentState.encryptWallet)}
|
||||
// label={componentState.failMessage ? __('Encrypting Wallet') : __('Apply')}
|
||||
// />
|
||||
// )}
|
||||
// </Form>
|
||||
// </section>
|
||||
|
||||
{/* Testing stuff and Diagnostics */}
|
||||
// {/* Testing stuff and Diagnostics */}
|
||||
|
||||
<section className="card card--section">
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('Sync Apply')}
|
||||
onClick={() => syncApply(syncHash, syncData, componentState.newPassword)}
|
||||
/>{' '}
|
||||
<Button button="primary" label={__('Check Sync')} onClick={() => checkSync()} />{' '}
|
||||
<Button button="primary" label={__('Setpass test')} onClick={() => setSavedPassword('testpass')} />{' '}
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('Getpass test')}
|
||||
onClick={() => getSavedPassword().then(p => setComponentState({ ...componentState, newPassword: p }))}
|
||||
/>{' '}
|
||||
<Button button="primary" label={__('Deletepass test')} onClick={() => deleteSavedPassword()} />{' '}
|
||||
<p>
|
||||
password:{' '}
|
||||
{componentState.newPassword
|
||||
? componentState.newPassword
|
||||
: componentState.newPassword === ''
|
||||
? 'blankString'
|
||||
: 'null'}
|
||||
</p>
|
||||
<p>encryptWallet: {String(componentState.encryptWallet)}</p>
|
||||
<p>enableSync: {String(componentState.enableSync)}</p>
|
||||
<p>syncApplyError: {String(syncApplyErrorMessage)}</p>
|
||||
<p>Has Synced: {String(hasSyncedWallet)}</p>
|
||||
<p>getSyncPending: {String(getSyncIsPending)}</p>
|
||||
<p>syncEnabled: {String(syncEnabled)}</p>
|
||||
<p>syncHash: {syncHash ? syncHash.slice(0, 10) : 'null'}</p>
|
||||
<p>syncData: {syncData ? syncData.slice(0, 10) : 'null'}</p>
|
||||
<p>walletEncrypted: {String(walletEncrypted)}</p>
|
||||
<p>emailRegistered: {String(isEmailVerified)}</p>
|
||||
<p>hashChanged: {String(hashChanged)}</p>
|
||||
</section>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
// <section className="card card--section">
|
||||
// <Button
|
||||
// button="primary"
|
||||
// label={__('Sync Apply')}
|
||||
// onClick={() => syncApply(syncHash, syncData, componentState.newPassword)}
|
||||
// />{' '}
|
||||
// <Button button="primary" label={__('Check Sync')} onClick={() => checkSync()} />{' '}
|
||||
// <Button button="primary" label={__('Setpass test')} onClick={() => setSavedPassword('testpass')} />{' '}
|
||||
// <Button
|
||||
// button="primary"
|
||||
// label={__('Getpass test')}
|
||||
// onClick={() => getSavedPassword().then(p => setComponentState({ ...componentState, newPassword: p }))}
|
||||
// />{' '}
|
||||
// <Button button="primary" label={__('Deletepass test')} onClick={() => deleteAuthToken()} />{' '}
|
||||
// <p>
|
||||
// password:{' '}
|
||||
// {componentState.newPassword
|
||||
// ? componentState.newPassword
|
||||
// : componentState.newPassword === ''
|
||||
// ? 'blankString'
|
||||
// : 'null'}
|
||||
// </p>
|
||||
// <p>encryptWallet: {String(componentState.encryptWallet)}</p>
|
||||
// <p>enableSync: {String(componentState.enableSync)}</p>
|
||||
// <p>syncApplyError: {String(syncApplyErrorMessage)}</p>
|
||||
// <p>Has Synced: {String(hasSyncedWallet)}</p>
|
||||
// <p>getSyncPending: {String(getSyncIsPending)}</p>
|
||||
// <p>syncEnabled: {String(syncEnabled)}</p>
|
||||
// <p>syncHash: {syncHash ? syncHash.slice(0, 10) : 'null'}</p>
|
||||
// <p>syncData: {syncData ? syncData.slice(0, 10) : 'null'}</p>
|
||||
// <p>walletEncrypted: {String(walletEncrypted)}</p>
|
||||
// <p>emailRegistered: {String(isEmailVerified)}</p>
|
||||
// <p>hashChanged: {String(hashChanged)}</p>
|
||||
// </section>
|
||||
// </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 { Formik } from 'formik';
|
||||
import { validateSendTx } from 'util/form-validation';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type DraftTransaction = {
|
||||
address: string,
|
||||
|
@ -36,71 +37,73 @@ class WalletSend extends React.PureComponent<Props> {
|
|||
const { balance } = this.props;
|
||||
|
||||
return (
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('Send Credits')}</h2>
|
||||
<p className="card__subtitle">{__('Send LBC to your friends or favorite creators.')}</p>
|
||||
<Card
|
||||
title={__('Send Credits')}
|
||||
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
|
||||
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}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
type="text"
|
||||
name="address"
|
||||
placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs"
|
||||
className="form-field--address"
|
||||
label={__('Recipient address')}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.address}
|
||||
/>
|
||||
</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>
|
||||
<FormField
|
||||
type="text"
|
||||
name="address"
|
||||
placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs"
|
||||
className="form-field--address"
|
||||
label={__('Recipient address')}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.address}
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,20 +158,9 @@ export const CLAIM_REWARD_FAILURE = 'CLAIM_REWARD_FAILURE';
|
|||
export const CLAIM_REWARD_CLEAR_ERROR = 'CLAIM_REWARD_CLEAR_ERROR';
|
||||
export const FETCH_REWARD_CONTENT_COMPLETED = 'FETCH_REWARD_CONTENT_COMPLETED';
|
||||
|
||||
// ShapeShift
|
||||
export const GET_SUPPORTED_COINS_START = 'GET_SUPPORTED_COINS_START';
|
||||
export const GET_SUPPORTED_COINS_SUCCESS = 'GET_SUPPORTED_COINS_SUCCESS';
|
||||
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';
|
||||
// Language
|
||||
export const DOWNLOAD_LANGUAGE_SUCCEEDED = 'DOWNLOAD_LANGUAGE_SUCCEEDED';
|
||||
export const DOWNLOAD_LANGUAGE_FAILED = 'DOWNLOAD_LANGUAGE_FAILED';
|
||||
|
||||
// Subscriptions
|
||||
export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const AUTH = 'auth';
|
||||
export const AUTH = 'signin';
|
||||
export const BACKUP = 'backup';
|
||||
export const CHANNEL = 'channel';
|
||||
export const DISCOVER = 'discover';
|
||||
|
|
|
@ -36,7 +36,6 @@ import { PersistGate } from 'redux-persist/integration/react';
|
|||
import 'scss/all.scss';
|
||||
|
||||
const APPPAGEURL = 'lbry://?';
|
||||
const COOKIE_EXPIRE_TIME = 60 * 60 * 24 * 365; // 1 year
|
||||
// @if TARGET='app'
|
||||
const { autoUpdater } = remote.require('electron-updater');
|
||||
autoUpdater.logger = remote.require('electron-log');
|
||||
|
@ -78,18 +77,19 @@ Lbryio.setOverride(
|
|||
throw new Error(__('auth_token is missing from response'));
|
||||
}
|
||||
|
||||
const newAuthToken = response.auth_token;
|
||||
authToken = newAuthToken;
|
||||
authToken = response.auth_token;
|
||||
|
||||
// @if TARGET='web'
|
||||
cookie.serialize('auth_token', authToken, {
|
||||
maxAge: COOKIE_EXPIRE_TIME,
|
||||
let date = new Date();
|
||||
date.setFullYear(date.getFullYear() + 1);
|
||||
document.cookie = cookie.serialize('auth_token', authToken, {
|
||||
expires: date,
|
||||
});
|
||||
// @endif
|
||||
|
||||
// @if TARGET='app'
|
||||
ipcRenderer.send('set-auth-token', authToken);
|
||||
// @endif
|
||||
resolve();
|
||||
|
||||
resolve(authToken);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
|
|
@ -12,19 +12,24 @@ const ModalFirstSubscription = (props: Props) => {
|
|||
|
||||
return (
|
||||
<Modal type="custom" isOpen contentLabel="Subscriptions 101" title={__('Subscriptions 101')}>
|
||||
<p>{__('You just subscribed to your first channel. Awesome!')}</p>
|
||||
<p>{__('A few quick things to know:')}</p>
|
||||
<p>
|
||||
{__(
|
||||
'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.'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{__(
|
||||
'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.'
|
||||
)}
|
||||
</p>
|
||||
<div className="modal__buttons">
|
||||
<div className="section__subtitle">
|
||||
<p>{__('You just subscribed to your first channel. Awesome!')}</p>
|
||||
<p>{__('A few quick things to know:')}</p>
|
||||
</div>
|
||||
<ol className="section">
|
||||
<li>
|
||||
{__(
|
||||
'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.'
|
||||
)}
|
||||
{__('(Only available on the desktop app.)')}
|
||||
</li>
|
||||
<li>
|
||||
{__(
|
||||
'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')} />
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Modal } from 'modal/modal';
|
||||
import { deleteSavedPassword } from 'util/saved-passwords';
|
||||
import { deleteAuthToken } from 'util/saved-passwords';
|
||||
|
||||
type Props = {
|
||||
closeModal: () => void,
|
||||
|
@ -18,7 +18,7 @@ class ModalPasswordUnsave extends React.PureComponent<Props> {
|
|||
confirmButtonLabel={__('Forget')}
|
||||
abortButtonLabel={__('Nevermind')}
|
||||
onConfirmed={() =>
|
||||
deleteSavedPassword().then(() => {
|
||||
deleteAuthToken().then(() => {
|
||||
this.props.closeModal();
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ import UserPhoneVerify from 'component/userPhoneVerify';
|
|||
import { Redirect } from 'react-router';
|
||||
|
||||
const LazyUserPhoneNew = React.lazy(() =>
|
||||
import(/* webpackChunkName: "userPhoneNew" */
|
||||
'component/userPhoneNew')
|
||||
import(
|
||||
/* webpackChunkName: "userPhoneNew" */
|
||||
'component/userPhoneNew'
|
||||
)
|
||||
);
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as PAGES from 'constants/pages';
|
||||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
@ -11,7 +12,7 @@ const perform = (dispatch, ownProps) => ({
|
|||
} = ownProps;
|
||||
const currentPath = pathname.split('/$/')[1];
|
||||
dispatch(doHideModal());
|
||||
history.push(`/$/auth?redirect=${currentPath}`);
|
||||
history.push(`/$/${PAGES.AUTH}?redirect=${currentPath}`);
|
||||
},
|
||||
closeModal: () => dispatch(doHideModal()),
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import React from 'react';
|
||||
import { Modal } from 'modal/modal';
|
||||
import Button from 'component/button';
|
||||
import { deleteSavedPassword } from 'util/saved-passwords';
|
||||
import { deleteAuthToken } from 'util/saved-passwords';
|
||||
|
||||
type Props = {
|
||||
closeModal: () => void,
|
||||
|
@ -24,7 +24,7 @@ class ModalWalletDecrypt extends React.PureComponent<Props, State> {
|
|||
const { props, state } = this;
|
||||
|
||||
if (state.submitted && props.walletDecryptSucceded === true) {
|
||||
deleteSavedPassword();
|
||||
deleteAuthToken();
|
||||
props.closeModal();
|
||||
props.updateWalletStatus();
|
||||
}
|
||||
|
|
|
@ -1,40 +1,27 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import RewardSummary from 'component/rewardSummary';
|
||||
import RewardTotal from 'component/rewardTotal';
|
||||
import Page from 'component/page';
|
||||
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
||||
import UserEmail from 'component/userEmail';
|
||||
import InvitePage from 'page/invite';
|
||||
// import YoutubeChannelList from 'component/youtubeChannelList';
|
||||
import InviteNew from 'component/inviteNew';
|
||||
import InviteList from 'component/inviteList';
|
||||
|
||||
type Props = {
|
||||
// ytChannels: Array<any>,
|
||||
};
|
||||
|
||||
const AccountPage = (props: Props) => {
|
||||
// const { ytChannels } = props;
|
||||
// const hasYoutubeChannels = Boolean(ytChannels.length);
|
||||
const AccountPage = () => {
|
||||
return (
|
||||
<Page>
|
||||
{/* @if TARGET='web' */}
|
||||
<UserEmail />
|
||||
{/* @endif */}
|
||||
<UnsupportedOnWeb />
|
||||
<div className={classnames({ 'card--disabled': IS_WEB })}>
|
||||
<div className="columns">
|
||||
<UserEmail />
|
||||
<div>
|
||||
<RewardSummary />
|
||||
<RewardTotal />
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div>
|
||||
<RewardSummary />
|
||||
<RewardTotal />
|
||||
</div>
|
||||
<div>
|
||||
<UserEmail />
|
||||
<InviteNew />
|
||||
</div>
|
||||
{/* {hasYoutubeChannels && <YoutubeChannelList />} */}
|
||||
<InvitePage />
|
||||
</div>
|
||||
<InviteList />
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountPage;
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectFollowedTags } from 'lbry-redux';
|
||||
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import DiscoverPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
followedTags: selectFollowedTags(state),
|
||||
subscribedChannels: selectSubscriptions(state),
|
||||
email: selectUserVerifiedEmail(state),
|
||||
});
|
||||
|
||||
const perform = {};
|
||||
|
|
|
@ -8,20 +8,20 @@ import Button from 'component/button';
|
|||
|
||||
type Props = {
|
||||
followedTags: Array<Tag>,
|
||||
email: string,
|
||||
};
|
||||
|
||||
function DiscoverPage(props: Props) {
|
||||
const { followedTags } = props;
|
||||
const { followedTags, email } = props;
|
||||
|
||||
return (
|
||||
<Page>
|
||||
{email && <TagsSelect showClose title={__('Customize Your Homepage')} />}
|
||||
<ClaimListDiscover
|
||||
hideCustomization={IS_WEB && !email}
|
||||
personalView
|
||||
tags={followedTags.map(tag => tag.name)}
|
||||
meta={<Button button="link" label={__('Customize')} navigate={`/$/${PAGES.FOLLOWING}`} />}
|
||||
injectedItem={
|
||||
<TagsSelect showClose title={__('Customize Your Homepage')} className="claim-preview--injected" />
|
||||
}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -124,7 +124,6 @@ class FilePage extends React.Component<Props> {
|
|||
nsfw,
|
||||
supportOption,
|
||||
} = this.props;
|
||||
|
||||
// File info
|
||||
const { signing_channel: signingChannel } = claim;
|
||||
const channelName = signingChannel && signingChannel.name;
|
||||
|
|
|
@ -35,14 +35,15 @@ function FileListPublished(props: Props) {
|
|||
<Paginate totalPages={Math.ceil(Number(urlTotal) / Number(PAGE_SIZE))} loading={fetching} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="main--empty">
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__("It looks like you haven't published anything to LBRY yet.")}</h2>
|
||||
<div className="card__actions card__actions--center">
|
||||
<section className="main--empty">
|
||||
<div className=" section--small">
|
||||
<h2 className="section__title--large">{__('Nothing published to LBRY yet.')}</h2>
|
||||
|
||||
<div className="section__actions">
|
||||
<Button button="primary" navigate="/$/publish" label={__('Publish something new')} />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as PAGES from 'constants/pages';
|
||||
import { connect } from 'react-redux';
|
||||
import { doFetchAccessToken, selectAccessToken, selectUser } from 'lbryinc';
|
||||
import { selectDaemonSettings } from 'redux/selectors/settings';
|
||||
|
@ -10,7 +11,7 @@ const select = state => ({
|
|||
});
|
||||
|
||||
const perform = (dispatch, ownProps) => ({
|
||||
doAuth: () => ownProps.history.push('/$/auth?redirect=help'),
|
||||
doAuth: () => ownProps.history.push(`/$/${PAGES.AUTH}?redirect=help`),
|
||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||
});
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||
import BusyIndicator from 'component/common/busy-indicator';
|
||||
import InviteNew from 'component/inviteNew';
|
||||
import InviteList from 'component/inviteList';
|
||||
import Page from 'component/page';
|
||||
|
||||
type Props = {
|
||||
isPending: boolean,
|
||||
|
@ -26,16 +27,17 @@ class InvitePage extends React.PureComponent<Props> {
|
|||
const { isPending, isFailed } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Page>
|
||||
{isPending && <BusyIndicator message={__('Checking your invite status')} />}
|
||||
{!isPending && isFailed && <span className="empty">{__('Failed to retrieve invite status.')}</span>}
|
||||
{!isPending && !isFailed && (
|
||||
<React.Fragment>
|
||||
{' '}
|
||||
<InviteNew />
|
||||
<InviteList />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import Button from 'component/button';
|
|||
import Page from 'component/page';
|
||||
import classnames from 'classnames';
|
||||
import { rewards as REWARD_TYPES } from 'lbryinc';
|
||||
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
||||
|
||||
type Props = {
|
||||
doAuth: () => void,
|
||||
|
@ -31,7 +30,6 @@ class RewardsPage extends PureComponent<Props> {
|
|||
componentDidMount() {
|
||||
this.props.fetchRewards();
|
||||
}
|
||||
|
||||
renderPageHeader() {
|
||||
const { user, daemonSettings } = this.props;
|
||||
|
||||
|
@ -48,7 +46,7 @@ class RewardsPage extends PureComponent<Props> {
|
|||
</p>
|
||||
|
||||
<Button
|
||||
navigate={`/$/${PAGES.AUTH}/signin?redirect=rewards`}
|
||||
navigate={`/$/${PAGES.AUTH}?redirect=/$/${PAGES.REWARDS}`}
|
||||
button="primary"
|
||||
label={__('Unlock Rewards')}
|
||||
/>
|
||||
|
@ -95,7 +93,7 @@ class RewardsPage extends PureComponent<Props> {
|
|||
renderUnclaimedRewards() {
|
||||
const { fetching, rewards, user, daemonSettings, claimed } = this.props;
|
||||
|
||||
if (daemonSettings && !daemonSettings.share_usage_data) {
|
||||
if (!IS_WEB && daemonSettings && !daemonSettings.share_usage_data) {
|
||||
return (
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('Disabled')}</h2>
|
||||
|
@ -150,7 +148,6 @@ class RewardsPage extends PureComponent<Props> {
|
|||
render() {
|
||||
return (
|
||||
<Page>
|
||||
{IS_WEB && <UnsupportedOnWeb />}
|
||||
{this.renderPageHeader()}
|
||||
{this.renderUnclaimedRewards()}
|
||||
{<RewardListClaimed />}
|
||||
|
|
|
@ -20,10 +20,11 @@ const select = (state, props) => {
|
|||
try {
|
||||
uri = normalizeURI(path);
|
||||
} catch (e) {
|
||||
// Probably an old channel url, redirect to the vanity channel
|
||||
// @routinghax
|
||||
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)}`;
|
||||
props.history.replace(`/${path.slice(0, match.index)}`);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectEmailToVerify, selectUser } from 'lbryinc';
|
||||
import { selectMyChannelClaims } from 'lbry-redux';
|
||||
import SignUpPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
email: selectEmailToVerify(state),
|
||||
user: selectUser(state),
|
||||
channels: selectMyChannelClaims(state),
|
||||
});
|
||||
const select = () => ({});
|
||||
const perform = () => ({});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
perform
|
||||
)(SignUpPage);
|
||||
|
|
|
@ -1,42 +1,12 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import UserSignIn from 'component/userSignIn';
|
||||
import UserFirstChannel from 'component/userFirstChannel';
|
||||
import UserVerify from 'component/userVerify';
|
||||
import Page from 'component/page';
|
||||
|
||||
type Props = {
|
||||
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}` : '/');
|
||||
}
|
||||
|
||||
export default function SignInPage() {
|
||||
return (
|
||||
<Page fullscreen className="main--auth-page">
|
||||
{showWelcome && (
|
||||
<div className="columns">
|
||||
{!hasVerifiedEmail && <UserSignIn />}
|
||||
{hasVerifiedEmail && channelCount === 0 && <UserFirstChannel />}
|
||||
<div style={{ width: '100%', height: '20rem', borderRadius: 20, backgroundColor: '#ffc7e6' }} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasVerifiedEmail && channelCount > 0 && <UserVerify />}
|
||||
<UserSignIn />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import TransactionList from 'component/transactionList';
|
||||
import Page from 'component/page';
|
||||
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
||||
|
||||
type Props = {
|
||||
fetchMyClaims: () => void,
|
||||
|
@ -26,12 +24,7 @@ class TransactionHistoryPage extends React.PureComponent<Props> {
|
|||
|
||||
return (
|
||||
<Page>
|
||||
{IS_WEB && <UnsupportedOnWeb />}
|
||||
<section
|
||||
className={classnames('card', {
|
||||
'card--disabled': IS_WEB,
|
||||
})}
|
||||
>
|
||||
<section className="card">
|
||||
<TransactionList
|
||||
transactions={filteredTransactionPage}
|
||||
transactionCount={filteredTransactionsCount}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { ipcRenderer, remote } from 'electron';
|
|||
import path from 'path';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import {
|
||||
Lbry,
|
||||
doBalanceSubscribe,
|
||||
|
@ -13,6 +14,8 @@ import {
|
|||
doError,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectClaimIsMine,
|
||||
doPopulateUserSettings,
|
||||
doFetchChannelListMine,
|
||||
} from 'lbry-redux';
|
||||
import Native from 'native';
|
||||
import { doFetchDaemonSettings } from 'redux/actions/settings';
|
||||
|
@ -28,10 +31,12 @@ import {
|
|||
selectUpgradeTimer,
|
||||
selectModal,
|
||||
} from 'redux/selectors/app';
|
||||
import { doAuthenticate } from 'lbryinc';
|
||||
import { Lbryio, doAuthenticate } from 'lbryinc';
|
||||
import { lbrySettings as config, version as appVersion } from 'package.json';
|
||||
import { push } from 'connected-react-router';
|
||||
import analytics from 'analytics';
|
||||
import { deleteAuthToken } from 'util/saved-passwords';
|
||||
import cookie from 'cookie';
|
||||
|
||||
// @if TARGET='app'
|
||||
const { autoUpdater } = remote.require('electron-updater');
|
||||
|
@ -322,13 +327,12 @@ export function doAlertError(errorList) {
|
|||
export function doDaemonReady() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
dispatch(doAuthenticate(appVersion));
|
||||
dispatch({ type: ACTIONS.DAEMON_READY });
|
||||
|
||||
// @if TARGET='app'
|
||||
dispatch(doFetchDaemonSettings());
|
||||
dispatch(doBalanceSubscribe());
|
||||
dispatch(doFetchDaemonSettings());
|
||||
dispatch(doFetchFileInfosAndPublishedClaims());
|
||||
if (!selectIsUpgradeSkipped(state)) {
|
||||
dispatch(doCheckUpgradeAvailable());
|
||||
|
@ -414,7 +418,7 @@ export function doConditionalAuthNavigate(newSession) {
|
|||
const modal = selectModal(state);
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { parseURI, ACTIONS as LBRY_REDUX_ACTIONS } from 'lbry-redux';
|
||||
import { VIEW_ALL } from 'constants/subscriptions';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
|
||||
|
@ -12,6 +13,7 @@ const defaultState: SubscriptionState = {
|
|||
loadingSuggested: false,
|
||||
firstRunCompleted: false,
|
||||
showSuggestedSubs: false,
|
||||
enabledChannelNotifications: [],
|
||||
};
|
||||
|
||||
export default handleActions(
|
||||
|
@ -134,6 +136,39 @@ export default handleActions(
|
|||
...state,
|
||||
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
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
@import 'component/file-render';
|
||||
@import 'component/form-field';
|
||||
@import 'component/header';
|
||||
@import 'component/icon';
|
||||
@import 'component/item-list';
|
||||
@import 'component/main';
|
||||
@import 'component/markdown-editor';
|
||||
|
@ -34,6 +35,7 @@
|
|||
@import 'component/pagination';
|
||||
@import 'component/placeholder';
|
||||
@import 'component/search';
|
||||
@import 'component/section';
|
||||
@import 'component/snack-bar';
|
||||
@import 'component/spinner';
|
||||
@import 'component/splash';
|
||||
|
|
|
@ -15,9 +15,16 @@
|
|||
}
|
||||
|
||||
.button--primary {
|
||||
background-color: $lbry-teal-5;
|
||||
|
||||
&:hover {
|
||||
background-color: $lbry-teal-4;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Play/View button that is overlayed ontop of the video player
|
||||
|
|
|
@ -55,6 +55,12 @@
|
|||
margin-right: auto;
|
||||
}
|
||||
|
||||
.card--inline {
|
||||
box-shadow: none;
|
||||
border-radius: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// C A R D
|
||||
// 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 {
|
||||
@extend .card__title;
|
||||
justify-content: space-between;
|
||||
|
@ -214,3 +214,29 @@
|
|||
opacity: 0.5;
|
||||
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 {
|
||||
border-color: lighten($lbry-black, 20%);
|
||||
border-radius: var(--input-border-radius);
|
||||
background-color: $lbry-white;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
|
@ -67,6 +68,7 @@ fieldset-section {
|
|||
label {
|
||||
width: auto;
|
||||
text-transform: none;
|
||||
color: lighten($lbry-black, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +92,6 @@ radio-element {
|
|||
}
|
||||
|
||||
label {
|
||||
color: lighten($lbry-black, 20%);
|
||||
margin-bottom: 0;
|
||||
margin-left: var(--spacing-miniscule);
|
||||
font-size: var(--font-body);
|
||||
|
@ -184,9 +185,12 @@ fieldset-group {
|
|||
height: var(--input-height);
|
||||
padding-right: 0;
|
||||
border: 1px solid;
|
||||
border-top-left-radius: var(--input-border-radius);
|
||||
border-bottom-left-radius: var(--input-border-radius);
|
||||
border-right: 0;
|
||||
border-color: $lbry-black;
|
||||
color: $lbry-gray-4;
|
||||
background-color: $lbry-white;
|
||||
|
||||
[data-mode='dark'] & {
|
||||
border-color: $lbry-gray-4;
|
||||
|
@ -275,7 +279,6 @@ fieldset-section {
|
|||
max-width: 12em;
|
||||
background-position: 95% center;
|
||||
background-size: 1.2rem;
|
||||
background-color: $lbry-white;
|
||||
|
||||
[data-mode='dark'] & {
|
||||
background-color: transparent;
|
||||
|
@ -305,7 +308,6 @@ fieldset-section {
|
|||
background-color: rgba($lbry-gray-1, 0.5);
|
||||
border: 1px solid $lbry-gray-1;
|
||||
color: $lbry-gray-5;
|
||||
flex: 1;
|
||||
padding: 0.2rem 0.75rem;
|
||||
text-overflow: ellipsis;
|
||||
user-select: text;
|
||||
|
@ -324,6 +326,10 @@ fieldset-section {
|
|||
margin-bottom: var(--spacing-large);
|
||||
}
|
||||
|
||||
.form-field--short {
|
||||
width: 25em;
|
||||
}
|
||||
|
||||
.form-field--price-amount {
|
||||
width: 7em;
|
||||
}
|
||||
|
|
|
@ -54,11 +54,15 @@
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.button {
|
||||
> .button:only-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.header__menu--small {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.header__navigation-arrows {
|
||||
display: flex;
|
||||
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 {
|
||||
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) {
|
||||
width: 100%;
|
||||
|
@ -43,7 +44,7 @@
|
|||
|
||||
.main--auth-page {
|
||||
max-width: 60rem;
|
||||
margin-top: calc(var(--spacing-main-padding) * 2);
|
||||
margin-top: var(--spacing-main-padding);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
@ -63,22 +64,15 @@
|
|||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.main--fullscreen {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 9999;
|
||||
background-color: $lbry-white;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
margin-top: 0;
|
||||
.main--contained {
|
||||
max-width: 35rem;
|
||||
min-width: 25rem;
|
||||
margin: auto;
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
* {
|
||||
z-index: 10000;
|
||||
}
|
||||
.main--full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main__status {
|
||||
|
|
|
@ -9,9 +9,22 @@
|
|||
|
||||
.navigation--placeholder {
|
||||
@extend .navigation;
|
||||
height: 80vh;
|
||||
background-color: $lbry-blue-1;
|
||||
padding: 2rem 1.5rem;
|
||||
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 {
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
table,
|
||||
.table {
|
||||
background-color: transparent;
|
||||
margin: var(--spacing-small) 0;
|
||||
padding-top: var(--spacing-small);
|
||||
|
||||
[data-mode='dark'] & {
|
||||
background-color: transparent;
|
||||
|
||||
th {
|
||||
border-bottom: 2px solid $lbry-white;
|
||||
}
|
||||
|
|
|
@ -15,8 +15,11 @@ $main: $lbry-teal-5;
|
|||
|
||||
.tags--remove {
|
||||
@extend .tags;
|
||||
@extend .ul--no-style;
|
||||
margin-bottom: var(--spacing-large);
|
||||
|
||||
.tag {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--spacing-small);
|
||||
}
|
||||
}
|
||||
|
||||
.tags--vertical {
|
||||
|
@ -31,8 +34,24 @@ $main: $lbry-teal-5;
|
|||
margin: var(--spacing-large) 0;
|
||||
}
|
||||
|
||||
.tags__empty-message {
|
||||
margin-top: var(--spacing-medium);
|
||||
.tags__input-wrapper {
|
||||
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 {
|
||||
|
@ -68,7 +87,11 @@ $main: $lbry-teal-5;
|
|||
}
|
||||
|
||||
.tag--add {
|
||||
background-color: lighten($lbry-teal-5, 60%);
|
||||
background-color: $lbry-teal-5;
|
||||
color: $lbry-white;
|
||||
.icon {
|
||||
stroke: $lbry-white;
|
||||
}
|
||||
|
||||
&.tag--mature {
|
||||
@extend .badge--mature;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-right: var(--spacing-main-padding);
|
||||
margin-right: var(--spacing-large);
|
||||
font-size: var(--font-label);
|
||||
|
||||
@media (max-width: 600px) {
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
.menu__title,
|
||||
.menu__link {
|
||||
color: lighten($lbry-black, 20%);
|
||||
color: $lbry-gray-5;
|
||||
|
||||
.icon {
|
||||
stroke: $lbry-gray-5;
|
||||
|
@ -104,7 +105,7 @@
|
|||
.menu__link {
|
||||
.icon {
|
||||
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-weight: 400;
|
||||
line-height: 1.5;
|
||||
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
||||
background-color: var(--color-background);
|
||||
|
||||
[data-mode='dark'] & {
|
||||
background-color: var(--dm-color-08);
|
||||
|
@ -48,13 +48,15 @@ p {
|
|||
|
||||
ul,
|
||||
ol {
|
||||
margin-bottom: var(--spacing-large);
|
||||
}
|
||||
|
||||
ul {
|
||||
ul,
|
||||
ol {
|
||||
list-style: initial;
|
||||
margin-bottom: var(--spacing-large);
|
||||
|
||||
li {
|
||||
list-style-position: outside;
|
||||
margin: var(--spacing-medium) 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,10 +34,12 @@ $large-breakpoint: 1921px;
|
|||
--font-label: 0.9em;
|
||||
--font-subtext: 1em;
|
||||
--font-title: 1.6em;
|
||||
--font-section-title: 2rem;
|
||||
--font-heading: 3rem;
|
||||
|
||||
// Color
|
||||
--color-background: #270f34;
|
||||
--color-background: #f7f7f7;
|
||||
--color-background--splash: #270f34;
|
||||
|
||||
// Dark Mode
|
||||
--dm-color-01: #ddd;
|
||||
|
|
|
@ -8,6 +8,8 @@ import thunk from 'redux-thunk';
|
|||
import { createHashHistory, createBrowserHistory } from 'history';
|
||||
import { routerMiddleware } from 'connected-react-router';
|
||||
import createRootReducer from './reducers';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import isEqual from 'util/deep-equal';
|
||||
|
||||
function isFunction(object) {
|
||||
return typeof object === 'function';
|
||||
|
@ -53,13 +55,12 @@ const whiteListedReducers = [
|
|||
// @if TARGET='app'
|
||||
'publish',
|
||||
'wallet',
|
||||
'tags',
|
||||
// 'fileInfo',
|
||||
// @endif
|
||||
'content',
|
||||
'subscriptions',
|
||||
'app',
|
||||
'search',
|
||||
'tags',
|
||||
'blocked',
|
||||
'settings',
|
||||
];
|
||||
|
@ -69,6 +70,7 @@ const transforms = [
|
|||
walletFilter,
|
||||
fileInfoFilter,
|
||||
blockedFilter,
|
||||
tagsFilter,
|
||||
// @endif
|
||||
appFilter,
|
||||
searchFilter,
|
||||
|
@ -106,6 +108,29 @@ const store = createStore(
|
|||
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);
|
||||
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(
|
||||
resolve => {
|
||||
ipcRenderer.once('delete-password-response', (event, success) => {
|
||||
resolve(success);
|
||||
// @if TARGET='app'
|
||||
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(false);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"404": "404",
|
||||
"Thumbnail Image": "Thumbnail Image",
|
||||
"OK": "OK",
|
||||
"Cancel": "Cancel",
|
||||
|
@ -708,6 +709,6 @@
|
|||
"Tip %amount% LBC": "Tip %amount% LBC",
|
||||
"Not enough credits": "Not enough credits",
|
||||
"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.",
|
||||
"Explore new content": "Explore new content"
|
||||
}
|
||||
"URI does not include name.": "URI does not include name.",
|
||||
"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-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":
|
||||
version "2.0.0"
|
||||
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"
|
||||
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":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
|
||||
|
@ -1057,11 +1030,6 @@
|
|||
"@types/minimatch" "*"
|
||||
"@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@*":
|
||||
version "3.0.3"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.7.tgz#1854f0a9aa8d2cd6818d607b3d091346c6730362"
|
||||
integrity sha512-on4MmIDgHXiuJDELPk1NFaKVUxxCFr37tm8E9yN6rAiF5Pzp/9bBfBHkoexqRiY+hk/Z04EJU9kKEb59YqJ82A==
|
||||
|
@ -5087,13 +5055,6 @@ fragment-cache@^0.2.1:
|
|||
dependencies:
|
||||
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:
|
||||
version "0.5.2"
|
||||
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"
|
||||
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:
|
||||
version "9.15.6"
|
||||
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"
|
||||
zstd-codec "^0.1.1"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#64383d57873ce59dea9df7216ee6cf52c4e95dc6:
|
||||
lbry-redux@lbryio/lbry-redux#42bf926138872d14523be7191694309be4f37605:
|
||||
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:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
lbryinc@lbryio/lbryinc#d250096a6fc5df16be4f82812ecce28d6e558b6e:
|
||||
lbryinc@lbryio/lbryinc#67bb3e215be3f13605c5e3f9f2b0e2fb880724cf:
|
||||
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:
|
||||
reselect "^3.0.0"
|
||||
|
||||
|
@ -8715,32 +8671,6 @@ please-upgrade-node@^3.0.2:
|
|||
dependencies:
|
||||
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:
|
||||
version "1.0.20"
|
||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.20.tgz#bea68632e54b2e13ab7b0c4775e9b41bf270e44a"
|
||||
|
@ -8750,16 +8680,6 @@ portfinder@^1.0.20:
|
|||
debug "^2.2.0"
|
||||
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:
|
||||
version "0.1.1"
|
||||
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:
|
||||
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:
|
||||
version "6.0.1"
|
||||
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"
|
||||
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:
|
||||
version "4.0.3"
|
||||
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"
|
||||
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
|
||||
|
||||
ts-essentials@^1.0.3:
|
||||
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:
|
||||
tslib@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
||||
|
|
Loading…
Reference in a new issue