onboarding + youtube transfer + channel page

This commit is contained in:
Sean Yesmunt 2019-09-26 12:28:08 -04:00
parent c0a8e02004
commit 4c014e3147
55 changed files with 775 additions and 337 deletions

View file

@ -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#04ae0913a444abac200731c7ed53796d763a0bbb",
"lbryinc": "lbryio/lbryinc#d99232ebc754a49649a2ff4132478415faef08e2",
"lbry-redux": "lbryio/lbry-redux#d44cd9ca56dee784dba42c0cc13061ae75cbd46c",
"lbryinc": "lbryio/lbryinc#368040d64658cf2a4b8a7a6725ec1787329ce65d",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",
"lodash-es": "^4.17.14",
@ -151,6 +151,7 @@
"raw-loader": "^2.0.0",
"rc-progress": "^2.0.6",
"react": "^16.8.2",
"react-confetti": "^4.0.1",
"react-dom": "^16.8.2",
"react-draggable": "^3.3.0",
"react-ga": "^2.5.7",

View file

@ -1,7 +1,7 @@
import * as SETTINGS from 'constants/settings';
import { hot } from 'react-hot-loader/root';
import { connect } from 'react-redux';
import { selectUser, doRewardList, doFetchRewardedContent, doFetchAccessToken, selectAccessToken } from 'lbryinc';
import { selectUser, doRewardList, doFetchRewardedContent, doFetchAccessToken } from 'lbryinc';
import { doFetchTransactions, doFetchChannelListMine } from 'lbry-redux';
import { makeSelectClientSetting, selectThemePath } from 'redux/selectors/settings';
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
@ -12,7 +12,6 @@ const select = state => ({
user: selectUser(state),
theme: selectThemePath(state),
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state),
accessToken: selectAccessToken(state),
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state),
});
@ -22,6 +21,8 @@ const perform = dispatch => ({
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
fetchTransactions: () => dispatch(doFetchTransactions()),
fetchAccessToken: () => dispatch(doFetchAccessToken()),
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
onSignedIn: () => dispatch(doSignIn()),
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
onSignedIn: () => dispatch(doSignIn()),

View file

@ -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 SyncBackgroundManager from 'component/syncBackgroundManager';
import Button from 'component/button';
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
@ -27,11 +28,13 @@ type Props = {
fetchRewardedContent: () => void,
fetchTransactions: () => void,
fetchAccessToken: () => void,
autoUpdateDownloaded: boolean,
isUpgradeAvailable: boolean,
fetchChannelListMine: () => void,
onSignedIn: () => void,
requestDownloadUpgrade: () => void,
fetchChannelListMine: () => void,
onSignedIn: () => void,
isUpgradeAvailable: boolean,
autoUpdateDownloaded: boolean,
};
function App(props: Props) {
@ -42,11 +45,11 @@ function App(props: Props) {
fetchTransactions,
user,
fetchAccessToken,
requestDownloadUpgrade,
autoUpdateDownloaded,
isUpgradeAvailable,
fetchChannelListMine,
onSignedIn,
autoUpdateDownloaded,
isUpgradeAvailable,
requestDownloadUpgrade,
} = props;
const appRef = useRef();
const isEnhancedLayout = useKonamiListener();
@ -118,6 +121,7 @@ function App(props: Props) {
<Router />
<ModalRouter />
<FileViewer pageUri={uri} />
<SyncBackgroundManager />
{/* @if TARGET='app' */}
{showUpgradeButton && (

View file

@ -1,4 +1,5 @@
// @flow
import type { Node } from 'react';
import React, { Fragment, useEffect, forwardRef } from 'react';
import classnames from 'classnames';
import { parseURI, convertToShareLink } from 'lbry-redux';
@ -45,6 +46,9 @@ type Props = {
channelIsBlocked: boolean,
isSubscribed: boolean,
beginPublish: string => void,
actions: boolean | Node | string | number,
properties: boolean | Node | string | number,
onClick?: any => any,
};
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
@ -70,12 +74,14 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
channelIsBlocked,
isSubscribed,
beginPublish,
actions,
properties,
onClick,
} = props;
const shouldFetch = claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta));
const abandoned = !isResolvingUri && !claim;
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
const showPublishLink = abandoned && placeholder === 'publish';
const includeChannelTooltip = type !== 'inline' && type !== 'tooltip';
const hideActions = type === 'small' || type === 'tooltip';
let name;
@ -90,6 +96,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
}
const isChannel = isValid ? parseURI(uri).isChannel : false;
const includeChannelTooltip = type !== 'inline' && type !== 'tooltip' && !isChannel;
const signingChannel = claim && claim.signing_channel;
let shouldHide =
placeholder !== 'loading' && ((abandoned && !showPublishLink) || (!claimIsMine && obscureNsfw && nsfw));
@ -129,8 +136,10 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
}
}
function onClick(e) {
if ((isChannel || title) && !pending) {
function handleOnClick(e) {
if (onClick) {
onClick(e);
} else if ((isChannel || title) && !pending) {
history.push(formatLbryUriForWeb(uri));
}
}
@ -161,7 +170,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
<li
ref={ref}
role="link"
onClick={pending || type === 'inline' ? undefined : onClick}
onClick={pending || type === 'inline' ? undefined : handleOnClick}
onContextMenu={handleContextMenu}
className={classnames('claim-preview', {
'claim-preview--small': type === 'small' || type === 'tooltip',
@ -178,15 +187,19 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
<div className="claim-preview-title">
{claim ? <TruncatedText text={title || claim.name} lines={1} /> : <span>{__('Nothing here')}</span>}
</div>
{!hideActions && (
<div className="card__actions--inline">
{isChannel && !channelIsBlocked && (
<SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
{actions !== undefined
? actions
: !hideActions && (
<div className="card__actions--inline">
{isChannel && !channelIsBlocked && !claimIsMine && (
<SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)}
{isChannel && !isSubscribed && !claimIsMine && (
<BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)}
{!isChannel && claim && <FileProperties uri={uri} />}
</div>
)}
{isChannel && !isSubscribed && <BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />}
{!isChannel && claim && <FileProperties uri={uri} />}
</div>
)}
</div>
<div className="claim-preview-properties">
@ -219,7 +232,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
</div>
)}
</div>
<ClaimTags uri={uri} type={type} />
{properties !== undefined ? properties : <ClaimTags uri={uri} type={type} />}
</div>
</div>
</li>

View file

@ -104,9 +104,11 @@ export class FormField extends React.PureComponent<Props> {
input = (
<Wrapper>
<checkbox-element {...elementProps}>
<input id={name} type="checkbox" {...inputProps} />
<label htmlFor={name}>{label}</label>
<checkbox-toggle onClick={inputProps.onChange} />
<input id={name} type="checkbox" {...inputProps} tabIndex={0} />
<label htmlFor={name} tabIndex={-1}>
{label}
</label>
<checkbox-toggle onClick={inputProps.onChange} tabIndex={-1} />
</checkbox-element>
</Wrapper>
);

View file

@ -303,6 +303,13 @@ export const icons = {
<line x1="15" y1="12" x2="3" y2="12" />
</g>
),
[ICONS.SIGN_IN]: buildIcon(
<g>
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
<polyline points="10 17 15 12 10 7" />
<line x1="15" y1="12" x2="3" y2="12" />
</g>
),
[ICONS.SIGN_OUT]: buildIcon(
<g>
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />

View file

@ -3,8 +3,8 @@ import { connect } from 'react-redux';
import { selectBalance, formatCredits } from 'lbry-redux';
import { selectUserVerifiedEmail } from 'lbryinc';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSignOut } from 'redux/actions/app';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import Header from './view';
const select = state => ({

View file

@ -13,11 +13,8 @@ import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import Tooltip from 'component/common/tooltip';
type Props = {
autoUpdateDownloaded: boolean,
balance: string,
isUpgradeAvailable: boolean,
roundedBalance: number,
downloadUpgradeRequested: any => void,
history: { push: string => void, goBack: () => void, goForward: () => void },
currentTheme: string,
automaticDarkModeEnabled: boolean,
@ -137,7 +134,10 @@ const Header = (props: Props) => {
{__('Sign Out')}
</MenuItem>
) : (
<MenuItem onSelect={() => {}} />
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.AUTH}`)}>
<Icon aria-hidden icon={ICONS.SIGN_IN} />
{__('Sign In')}
</MenuItem>
)}
</MenuList>
</Menu>

View file

@ -77,7 +77,7 @@ class InviteNew extends React.PureComponent<Props> {
<Card
title={__('Invite a Friend')}
subtitle={__('When your friends start using LBRY, the network gets stronger!')}
body={
actions={
<React.Fragment>
<FormInviteNew
errorMessage={errorMessage}

View file

@ -16,7 +16,7 @@ class RewardSummary extends React.Component<Props> {
const hasRewards = unclaimedRewardAmount > 0;
return (
<Card
title={__('Rewards')}
title={__('Available Rewards')}
subtitle={
<React.Fragment>
{fetching && __('You have...')}

View file

@ -24,6 +24,7 @@ import FollowingPage from 'page/following';
import ListBlockedPage from 'page/listBlocked';
import FourOhFourPage from 'page/fourOhFour';
import SignInPage from 'page/signIn';
import ChannelsPage from 'page/channels';
// Tell the browser we are handling scroll restoration
if ('scrollRestoration' in history) {
@ -89,6 +90,7 @@ function AppRouter(props: Props) {
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET_SEND}`} exact component={WalletSendPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET_RECEIVE}`} exact component={WalletReceivePage} />
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
{/* 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} />

View file

@ -49,6 +49,9 @@ function SideBar(props: Props) {
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
},
// @endif
{
...buildLink(PAGES.CHANNELS, __('Channels'), ICONS.CHANNEL),
},
{
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH),
},

View file

@ -124,6 +124,11 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
clearTimeout(this.timeout);
}
//
//
// Try to unlock by default here
//
//
// Make sure there isn't another active modal (like INCOMPATIBLE_DAEMON)
if (launchedModal === false && !modal) {
this.setState({ launchedModal: true }, () => notifyUnlockWallet());

View file

@ -0,0 +1,24 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { selectBalance } from 'lbry-redux';
import { doGetSync, selectSyncHash, selectUserVerifiedEmail } from 'lbryinc';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import WalletSecurityAndSync from './view';
const select = state => ({
balance: selectBalance(state),
verifiedEmail: selectUserVerifiedEmail(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
hasSyncHash: selectSyncHash(state),
});
const perform = dispatch => ({
getSync: password => dispatch(doGetSync(password)),
setSync: value => dispatch(doSetClientSetting(SETTINGS.ENABLE_SYNC, value)),
});
export default connect(
select,
perform
)(WalletSecurityAndSync);

View file

@ -0,0 +1,22 @@
// @flow
import React from 'react';
type Props = {
syncEnabled: boolean,
verifiedEmail?: string,
getSync: () => void,
};
function SyncBackgroundManager(props: Props) {
const { syncEnabled, getSync, verifiedEmail } = props;
React.useEffect(() => {
if (syncEnabled && verifiedEmail) {
getSync();
}
}, [syncEnabled, verifiedEmail, getSync]);
return null;
}
export default SyncBackgroundManager;

View file

@ -0,0 +1,18 @@
import { connect } from 'react-redux';
import { doGetSync, selectGetSyncIsPending } from 'lbryinc';
import { doSetClientSetting } from 'redux/actions/settings';
import SyncPassword from './view';
const select = state => ({
getSyncIsPending: selectGetSyncIsPending(state),
});
const perform = dispatch => ({
getSync: password => dispatch(doGetSync(password)),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
});
export default connect(
select,
perform
)(SyncPassword);

View file

@ -0,0 +1,54 @@
// @flow
import React from 'react';
import { Form, FormField } from 'component/common/form';
import Button from 'component/button';
import Card from 'component/common/card';
import { setSavedPassword } from 'util/saved-passwords';
type Props = {
getSync: (?string) => void,
getSyncIsPending: boolean,
};
function SyncPassword(props: Props) {
const { getSync, getSyncIsPending } = props;
const [password, setPassword] = React.useState('');
const [rememberPassword, setRememberPassword] = React.useState(true);
function handleSubmit() {
if (rememberPassword) {
setSavedPassword(password);
}
getSync(password);
}
return (
<Form onSubmit={handleSubmit}>
<Card
title={__('Enter Your LBRY Password')}
actions={
<div>
<FormField
type="password"
label={__('Password')}
value={password}
onChange={e => setPassword(e.target.value)}
/>
<FormField
type="checkbox"
label={__('Remember My Password')}
checked={rememberPassword}
onChange={() => setRememberPassword(!rememberPassword)}
/>
<div className="card__actions">
<Button type="submit" button="primary" label={__('Continue')} disabled={!password || getSyncIsPending} />
</div>
</div>
}
/>
</Form>
);
}
export default SyncPassword;

View file

@ -75,20 +75,18 @@ export default function TagSelect(props: Props) {
}
body={
<React.Fragment>
<section className="section">
<TagsSearch
onRemove={handleTagClick}
onSelect={onSelect}
suggestMature={suggestMature && !hasMatureTag}
tagsPasssedIn={tagsToDisplay}
/>
{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>
<TagsSearch
onRemove={handleTagClick}
onSelect={onSelect}
suggestMature={suggestMature && !hasMatureTag}
tagsPasssedIn={tagsToDisplay}
/>
{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>
)}
</React.Fragment>
}
/>

View file

@ -39,8 +39,10 @@ function UserEmail(props: Props) {
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.'
)}
body={
isVerified ? (
actions={
!isVerified ? (
<Button button="primary" label={__('Add Email')} navigate={`/$/${PAGES.AUTH}`} />
) : (
<FormField
type="text"
className="form-field--copyable"
@ -58,9 +60,8 @@ function UserEmail(props: Props) {
inputButton={<UserSignOutButton button="inverse" />}
value={email || ''}
/>
) : null
)
}
actions={!isVerified ? <Button button="primary" label={__('Add Email')} navigate={`/$/${PAGES.AUTH}`} /> : null}
/>
);
}

View file

@ -1,14 +1,21 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { selectBalance } from 'lbry-redux';
import { selectEmailNewIsPending, selectEmailNewErrorMessage, doUserEmailNew } from 'lbryinc';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetClientSetting } from 'redux/actions/settings';
import UserEmailNew from './view';
const select = state => ({
isPending: selectEmailNewIsPending(state),
errorMessage: selectEmailNewErrorMessage(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
balance: selectBalance(state),
});
const perform = dispatch => ({
addUserEmail: email => dispatch(doUserEmailNew(email)),
setSync: value => dispatch(doSetClientSetting(SETTINGS.ENABLE_SYNC, value)),
});
export default connect(

View file

@ -4,19 +4,19 @@ import { FormField, Form } from 'component/common/form';
import Button from 'component/button';
import { Lbryio } from 'lbryinc';
import analytics from 'analytics';
import { EMAIL_REGEX } from 'constants/email';
type Props = {
errorMessage: ?string,
isPending: boolean,
addUserEmail: string => void,
syncEnabled: boolean,
setSync: boolean => void,
balance: number,
};
// "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 { errorMessage, isPending, addUserEmail, syncEnabled, setSync, balance } = props;
const [newEmail, setEmail] = useState('');
const valid = newEmail.match(EMAIL_REGEX);
@ -29,6 +29,12 @@ function UserEmailNew(props: Props) {
// @endif
}
React.useEffect(() => {
if (syncEnabled && balance) {
setSync(false);
}
}, [balance, syncEnabled, setSync]);
return (
<div>
<h1 className="section__title--large">{__('Welcome To LBRY')}</h1>
@ -39,12 +45,23 @@ function UserEmailNew(props: Props) {
className="form-field--short"
placeholder={__('hotstuff_96@hotmail.com')}
type="email"
id="sign_up_email"
name="sign_up_email"
label={__('Email')}
value={newEmail}
error={errorMessage}
onChange={e => setEmail(e.target.value)}
/>
<FormField
type="checkbox"
name="sync_checkbox"
label={__('Sync your balance between devices')}
helper={
balance > 0 ? __('This is only available for empty wallets') : __('Maybe some more text about something')
}
checked={syncEnabled}
onChange={() => setSync(!syncEnabled)}
disabled={balance > 0}
/>
<div className="card__actions">
<Button button="primary" type="submit" label={__('Continue')} disabled={!newEmail || !valid || isPending} />
</div>

View file

@ -1,3 +1,4 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import {
selectEmailToVerify,
@ -7,12 +8,19 @@ import {
selectClaimedRewards,
rewards as REWARD_TYPES,
doClaimRewardType,
doUserFetch,
selectUserIsPending,
selectYoutubeChannels,
selectGetSyncIsPending,
selectGetSyncErrorMessage,
selectSyncHash,
} from 'lbryinc';
import { selectMyChannelClaims, selectBalance, selectFetchingMyChannels } from 'lbry-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import UserSignIn from './view';
const select = state => ({
email: selectEmailToVerify(state),
emailToVerify: selectEmailToVerify(state),
user: selectUser(state),
accessToken: selectAccessToken(state),
channels: selectMyChannelClaims(state),
@ -22,9 +30,16 @@ const select = state => ({
}),
balance: selectBalance(state),
fetchingChannels: selectFetchingMyChannels(state),
youtubeChannels: selectYoutubeChannels(state),
userFetchPending: selectUserIsPending(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
syncIsPending: selectGetSyncIsPending(state),
getSyncError: selectGetSyncErrorMessage(state),
syncHash: selectSyncHash(state),
});
const perform = dispatch => ({
fetchUser: () => dispatch(doUserFetch()),
claimReward: () =>
dispatch(
doClaimRewardType(REWARD_TYPES.TYPE_CONFIRM_EMAIL, {

View file

@ -4,24 +4,38 @@ 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';
import UserVerify from 'component/userVerify';
import Spinner from 'component/spinner';
import YoutubeTransferWelcome from 'component/youtubeTransferWelcome';
import SyncPassword from 'component/syncPassword';
/*
- Brand new user
- Brand new user, not auto approved
- Second device (first time user), first device has a password + rewards not approved
- Second device (first time user), first device has a password + rewards approved
*/
type Props = {
user: ?User,
email: ?string,
fetchingChannels: boolean,
emailToVerify: ?string,
channels: ?Array<string>,
balance: ?number,
fetchingChannels: boolean,
claimingReward: boolean,
claimReward: () => void,
fetchUser: () => void,
claimedRewards: Array<Reward>,
history: { replace: string => void },
location: { search: string },
youtubeChannels: Array<any>,
syncIsPending: boolean,
getSyncError: ?string,
hasSyncedSuccessfully: boolean,
};
function useFetched(fetching) {
@ -38,64 +52,96 @@ function useFetched(fetching) {
}
function UserSignIn(props: Props) {
const { email, user, channels, claimingReward, claimReward, claimedRewards, balance, history, location } = props;
const {
emailToVerify,
user,
claimingReward,
claimedRewards,
channels,
claimReward,
balance,
history,
location,
fetchUser,
youtubeChannels,
syncEnabled,
syncIsPending,
getSyncError,
syncHash,
fetchingChannels,
} = 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 hasFetchedReward = useFetched(claimingReward);
// const hasFetchedSync = useFetched(syncIsPending);
// const hasTriedSyncForReal = syncEnabled && hasFetchedSync;
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]);
const hasYoutubeChannels = youtubeChannels && youtubeChannels.length;
const hasTransferrableYoutubeChannels = hasYoutubeChannels && youtubeChannels.some(channel => channel.transferable);
const hasPendingYoutubeTransfer =
hasYoutubeChannels && youtubeChannels.some(channel => channel.transfer_state === 'pending_transfer');
React.useEffect(() => {
if (hasVerifiedEmail && balance !== undefined && !hasClaimedEmailAward && !hasFetchedReward) {
memoizedClaimReward();
if (
hasVerifiedEmail &&
balance !== undefined &&
!hasClaimedEmailAward &&
!hasFetchedReward &&
(!syncEnabled || (syncEnabled && syncHash))
) {
claimReward();
}
}, [hasVerifiedEmail, memoizedClaimReward, balance, hasClaimedEmailAward, hasFetchedReward]);
}, [hasVerifiedEmail, claimReward, balance, hasClaimedEmailAward, hasFetchedReward, syncEnabled, syncHash]);
if (
!user ||
(balance === 0 && !hasFetchedReward) ||
(hasVerifiedEmail && balance === undefined) ||
(hasVerifiedEmail && !hasFetchedChannels)
) {
return (
<div className="main--empty">
<Spinner delayed />
</div>
);
}
React.useEffect(() => {
fetchUser();
}, [fetchUser]);
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>
{hasVerifiedEmail && !rewardsApproved ? (
<UserVerify />
) : (
<div className="main--contained">
{!email && !hasVerifiedEmail && <UserEmailNew />}
{email && !hasVerifiedEmail && <UserEmailVerify />}
{hasVerifiedEmail && balance && balance > 0 && channelCount === 0 && <UserFirstChannel />}
const SIGN_IN_FLOW = [
!emailToVerify && !hasVerifiedEmail && <UserEmailNew />,
emailToVerify && !hasVerifiedEmail && <UserEmailVerify />,
hasVerifiedEmail && !rewardsApproved && <UserVerify />,
getSyncError && !syncHash && <SyncPassword />,
hasVerifiedEmail && balance > DEFAULT_BID_FOR_FIRST_CHANNEL && channelCount === 0 && !hasYoutubeChannels && (
<UserFirstChannel />
),
hasVerifiedEmail && hasYoutubeChannels && (hasTransferrableYoutubeChannels || hasPendingYoutubeTransfer) && (
<YoutubeTransferWelcome />
),
hasVerifiedEmail &&
balance === 0 &&
!getSyncError &&
(fetchingChannels ||
!hasFetchedReward ||
claimingReward ||
syncIsPending ||
(syncEnabled && !syncHash) ||
// Just claimed the email award, wait until the balance updates to move forward
(balance === 0 && hasFetchedReward && hasClaimedEmailAward)) && (
<div className="main--empty">
<Spinner />
</div>
)}
</section>
);
),
];
let componentToRender;
for (var i = SIGN_IN_FLOW.length - 1; i > -1; i--) {
const Component = SIGN_IN_FLOW[i];
if (Component) {
componentToRender = Component;
break;
}
}
if (!componentToRender) {
history.replace(redirect || '/');
}
return <section className="main--contained">{componentToRender}</section>;
}
export default withRouter(UserSignIn);

View file

@ -97,7 +97,6 @@ class WalletSendTip extends React.PureComponent<Props, State> {
inputButton={
<Button
button="primary"
type="submit"
label={__('Send')}
disabled={isPending || tipError || !tipAmount}
onClick={this.handleSendButtonClicked}

View file

@ -1,59 +0,0 @@
// @flow
import React from 'react';
import Button from 'component/button';
type Channel = {
yt_channel_name: string,
lbry_channel_name: string,
channel_claim_id: string,
sync_status: string,
status_token: string,
transferable: boolean,
transfer_state: string,
publish_to_address: Array<string>,
};
type Props = {
channel: Channel,
};
export default function YoutubeChannelItem(props: Props) {
const {
yt_channel_name: ytName,
lbry_channel_name: lbryName,
sync_status: syncStatus,
status_token: statusToken,
transferable,
transfer_state: transferState,
} = props.channel;
const LBRY_YT_URL = 'https://lbry.com/youtube/status/';
const NOT_TRANSFERED = 'not_transferred';
const PENDING_TRANSFER = 'pending_transfer';
const COMPLETED_TRANSFER = 'completed_transfer';
function renderSwitch(param) {
switch (param) {
case NOT_TRANSFERED:
return __('Not Transferred');
case PENDING_TRANSFER:
return __('Pending Transfer');
case COMPLETED_TRANSFER:
return __('Completed Transfer');
}
}
// | Youtube Name | LBRY Name | SyncStatus | TransferStatus |
return (
<tr>
<td>{ytName}</td>
<td>
<Button button={'link'} navigate={`lbry://${lbryName}`} label={lbryName} />
</td>
<td>
<Button button={'link'} href={`${LBRY_YT_URL}${statusToken}`} label={syncStatus} />
</td>
<td>{transferable ? renderSwitch(transferState) : __('Not Transferable')}</td>
</tr>
);
}

View file

@ -1,70 +0,0 @@
// @flow
import * as React from 'react';
import YoutubeChannelListItem from './internal/youtubeChannel';
import Button from 'component/button';
import Spinner from 'component/spinner';
type Props = {
ytChannels: Array<any>,
ytImportPending: boolean,
userFetchPending: boolean,
claimChannels: () => void,
updateUser: () => void,
};
export default function YoutubeChannelList(props: Props) {
const { ytChannels, ytImportPending, userFetchPending, claimChannels, updateUser } = props;
const hasChannels = ytChannels && ytChannels.length;
const transferEnabled = ytChannels && ytChannels.some(el => el.transferable === true);
return (
hasChannels && (
<section className="card card--section">
<h2 className="card__title--between">
<span>Synced Youtube Channels{userFetchPending && <Spinner type="small" />}</span>
<div className="card__actions--inline">
<Button button="inverse" onClick={updateUser} label={__('Refresh')} />
</div>
</h2>
{transferEnabled && !IS_WEB && (
<p className="card__subtitle">LBRY is currently holding channels you can take control of.</p>
)}
{!transferEnabled && !IS_WEB && (
<p className="card__subtitle">LBRY is currently holding channels but none are ready for transfer yet.</p>
)}
{IS_WEB && (
<p className="card__subtitle">
{__(`LBRY.tv can't import accounts yet. `)}
<Button button="link" label={__('Download the app')} href="https://lbry.com/get" />
</p>
)}
<table className="table">
<thead>
<tr>
<th>{__('Youtube Name')}</th>
<th>{__('LBRY Name')} </th>
<th>{__('Sync Status')} </th>
<th>{__('Transfer Status')}</th>
</tr>
</thead>
<tbody>
{ytChannels.map(channel => (
<YoutubeChannelListItem
key={`yt${channel.yt_channel_name}${channel.lbry_channel_name}`}
channel={channel}
/>
))}
</tbody>
</table>
<div className="card__actions">
<Button
disabled={IS_WEB || !transferEnabled || ytImportPending}
button="primary"
onClick={claimChannels}
label={__('Claim Channels')}
/>
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/youtube#transfer" />
</div>
</section>
)
);
}

View file

@ -0,0 +1,29 @@
import { connect } from 'react-redux';
import {
selectYoutubeChannels,
selectYTImportPending,
selectUserIsPending,
doClaimYoutubeChannels,
doUserFetch,
selectYTImportVideosComplete,
doCheckYoutubeTransfer,
} from 'lbryinc';
import YoutubeChannelList from './view';
const select = state => ({
youtubeChannels: selectYoutubeChannels(state),
ytImportPending: selectYTImportPending(state),
userFetchPending: selectUserIsPending(state),
videosImported: selectYTImportVideosComplete(state),
});
const perform = dispatch => ({
claimChannels: () => dispatch(doClaimYoutubeChannels()),
updateUser: () => dispatch(doUserFetch()),
checkYoutubeTransfer: () => dispatch(doCheckYoutubeTransfer()),
});
export default connect(
select,
perform
)(YoutubeChannelList);

View file

@ -0,0 +1,121 @@
// @flow
import * as React from 'react';
import Button from 'component/button';
import ClaimPreview from 'component/claimPreview';
import Card from 'component/common/card';
type Props = {
youtubeChannels: Array<any>,
ytImportPending: boolean,
claimChannels: () => void,
updateUser: () => void,
checkYoutubeTransfer: () => void,
videosImported: ?Array<number>, // [currentAmountImported, totalAmountToImport]
};
const LBRY_YT_URL = 'https://lbry.com/youtube/status/';
const NOT_TRANSFERED = 'not_transferred';
const PENDING_TRANSFER = 'pending_transfer';
const COMPLETED_TRANSFER = 'completed_transfer';
export default function YoutubeTransferStatus(props: Props) {
const { youtubeChannels, ytImportPending, claimChannels, videosImported, checkYoutubeTransfer, updateUser } = props;
const hasChannels = youtubeChannels && youtubeChannels.length;
const transferEnabled = youtubeChannels && youtubeChannels.some(el => el.transferable === true);
const transferComplete =
youtubeChannels &&
youtubeChannels.some(({ transfer_state: transferState }) => transferState === COMPLETED_TRANSFER);
let youtubeUrls =
youtubeChannels &&
youtubeChannels.map(
({ lbry_channel_name: channelName, channel_claim_id: claimId }) => `lbry://${channelName}#${claimId}`
);
let total;
let complete;
if (!transferComplete && videosImported) {
complete = videosImported[0];
total = videosImported[1];
}
function getMessage(channel) {
const { transferable, transfer_state: transferState, sync_status: syncStatus } = channel;
if (!transferable) {
switch (transferState) {
case NOT_TRANSFERED:
return syncStatus[0].toUpperCase() + syncStatus.slice(1);
case PENDING_TRANSFER:
return __('Transfer in progress');
case COMPLETED_TRANSFER:
return __('Completed transfer');
}
} else {
return __('Ready to transfer');
}
}
React.useEffect(() => {
// If a channel is transferrable, theres nothing to check
if (!transferComplete) {
checkYoutubeTransfer();
let interval = setInterval(() => {
checkYoutubeTransfer();
updateUser();
}, 60 * 1000);
return () => {
clearInterval(interval);
};
}
}, [transferComplete, checkYoutubeTransfer, updateUser]);
return (
hasChannels &&
!transferComplete && (
<div>
<Card
title={youtubeUrls.length > 1 ? __('Your YouTube Channels') : __('Your YouTube Channel')}
subtitle={
<span>
{__('Your videos are currently being transferred. There is nothing else for you to do.')}{' '}
<Button button="link" href={LBRY_YT_URL} label={__('Learn more')} />.
</span>
}
body={
<section>
{youtubeUrls.map((url, index) => {
const channel = youtubeChannels[index];
const transferState = getMessage(channel);
return (
<div
key={url}
style={{ border: '1px solid #ccc', borderRadius: 'var(--card-radius)', marginBottom: '1rem' }}
>
<ClaimPreview uri={url} actions={<span className="help">{transferState}</span>} properties={''} />
</div>
);
})}
{videosImported && (
<div className="section help">
{complete} / {total} {__('videos transferred')}
</div>
)}
</section>
}
actions={
transferEnabled &&
!ytImportPending && (
<div className="card__actions">
<Button button="primary" onClick={claimChannels} label={__('Claim Channels')} />
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/youtube#transfer" />
</div>
)
}
/>
</div>
)
);
}

View file

@ -6,12 +6,11 @@ import {
doClaimYoutubeChannels,
doUserFetch,
} from 'lbryinc';
import YoutubeChannelList from './view';
const select = state => ({
ytChannels: selectYoutubeChannels(state),
ytImportPending: selectYTImportPending(state),
youtubeChannels: selectYoutubeChannels(state),
youtubeImportPending: selectYTImportPending(state),
userFetchPending: selectUserIsPending(state),
});

View file

@ -0,0 +1,76 @@
// @flow
import * as PAGES from 'constants/pages';
import React from 'react';
import classnames from 'classnames';
import ClaimPreview from 'component/claimPreview';
import Button from 'component/button';
import Confetti from 'react-confetti';
type Props = {
youtubeChannels: Array<{ lbry_channel_name: string, channel_claim_id: string, transfer_state: string }>,
claimChannels: () => void,
};
export default function UserYoutubeTransfer(props: Props) {
const { youtubeChannels, claimChannels } = props;
const hasYoutubeChannels = youtubeChannels && youtubeChannels.length;
const hasPendingYoutubeTransfer =
hasYoutubeChannels && youtubeChannels.some(channel => channel.transfer_state === 'pending_transfer');
return (
<div>
<div className="section__header">
{!hasPendingYoutubeTransfer ? (
<React.Fragment>
<h1 className="section__title--large">{__('Welcome back!')}</h1>
<p className="section__subtitle">{__('Your channel is ready to be sent over.')}</p>
</React.Fragment>
) : (
<React.Fragment>
<h1 className="section__title--large">{__('Good to Go!')}</h1>
<p className="section__subtitle">
{__('You now control your channel and your videos are being transferred to your account.')}
</p>
</React.Fragment>
)}
</div>
<section className="section">
{youtubeChannels.map(({ lbry_channel_name: channelName, channel_claim_id: claimId }) => (
<div key={channelName} className={classnames('card--claim-preview-wrap')}>
<ClaimPreview disabled onClick={() => {}} actions={false} uri={`lbry://${channelName}#${claimId}`} />
</div>
))}
</section>
{hasPendingYoutubeTransfer ? (
<section className="section">
<div className="section__header">
<h1 className="section__title">{__('Transfer In Progress...')}</h1>
<p className="section__subtitle">{__('You can now publish and comment using your official channel.')}</p>
</div>
<div className="card__actions">
<Button
button="primary"
label={youtubeChannels.length > 1 ? __('View Your Channels') : __('View Your Channel')}
navigate={`/$/${PAGES.CHANNELS}`}
/>
</div>
</section>
) : (
<section className="section">
<div className="section__header">
<h1 className="section__title">{__('Begin Transfer')}</h1>
<p className="section__subtitle">{__('Do it to it.')}</p>
</div>
<div className="section__actions">
<Button button="primary" label={__('Transfer')} onClick={claimChannels} />
</div>
</section>
)}
{hasPendingYoutubeTransfer && <Confetti recycle={false} style={{ position: 'fixed' }} />}
</div>
);
}

View file

@ -0,0 +1,3 @@
// "Email regex that 99.99% works"
// https://emailregex.com/
export 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])+)\])/;

View file

@ -77,3 +77,4 @@ export const VIEW = 'View';
export const EYE = 'Eye';
export const EYE_OFF = 'EyeOff';
export const SIGN_OUT = 'SignOut';
export const SIGN_IN = 'SignIn';

View file

@ -23,3 +23,4 @@ export const WALLET = 'wallet';
export const WALLET_SEND = 'wallet/send';
export const WALLET_RECEIVE = 'wallet/receive';
export const BLOCKED = 'blocked';
export const CHANNELS = 'channels';

View file

@ -20,3 +20,4 @@ export const HIDE_BALANCE = 'hide_balance';
export const HIDE_SPLASH_ANIMATION = 'hide_splash_animation';
export const FLOATING_PLAYER = 'floating_player';
export const DARK_MODE_TIMES = 'dark_mode_times';
export const ENABLE_SYNC = 'enable_sync';

View file

@ -1,10 +1,11 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
import { deleteAuthToken } from 'util/saved-passwords';
import { deleteSavedPassword } from 'util/saved-passwords';
type Props = {
closeModal: () => void,
callback?: () => void,
};
class ModalPasswordUnsave extends React.PureComponent<Props> {
@ -18,8 +19,11 @@ class ModalPasswordUnsave extends React.PureComponent<Props> {
confirmButtonLabel={__('Forget')}
abortButtonLabel={__('Nevermind')}
onConfirmed={() =>
deleteAuthToken().then(() => {
deleteSavedPassword().then(() => {
this.props.closeModal();
if (this.props.callback) {
this.props.callback();
}
})
}
onAborted={this.props.closeModal}

View file

@ -67,11 +67,11 @@ class ModalWalletUnlock extends React.PureComponent<Props, State> {
onConfirmed={() => unlockWallet(password)}
onAborted={quit}
>
<Form onSubmit={() => unlockWallet(password)}>
<p>
{__('Your wallet has been encrypted with a local password. Please enter your wallet password to proceed.')}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />.
</p>
<p>
{__('Your wallet has been encrypted with a local password. Please enter your wallet password to proceed.')}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />.
</p>
<Form className="section" onSubmit={() => unlockWallet(password)}>
<FormField
autoFocus
error={walletUnlockSucceded === false ? 'Incorrect Password' : false}
@ -88,7 +88,6 @@ class ModalWalletUnlock extends React.PureComponent<Props, State> {
name="wallet-remember-password"
onChange={event => this.onChangeRememberPassword(event)}
checked={rememberPassword}
helper={__('You will no longer see this at startup')}
/>
</fieldset-section>
</Form>

View file

@ -1,10 +1,7 @@
import { connect } from 'react-redux';
import AccountPage from './view';
import { selectYoutubeChannels } from 'lbryinc';
const select = state => ({
ytChannels: selectYoutubeChannels(state),
});
const select = state => ({});
export default connect(
select,

View file

@ -7,16 +7,16 @@ import UserEmail from 'component/userEmail';
import InviteNew from 'component/inviteNew';
import InviteList from 'component/inviteList';
const AccountPage = () => {
const AccountPage = (props: Props) => {
return (
<Page>
<div className="columns">
<div className="columns section">
<div>
<RewardSummary />
<RewardTotal />
<UserEmail />
</div>
<div>
<UserEmail />
<RewardSummary />
<InviteNew />
</div>
</div>

View file

@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import { selectMyChannelClaims, doFetchChannelListMine, selectFetchingMyChannels } from 'lbry-redux';
import { selectYoutubeChannels } from 'lbryinc';
import ChannelsPage from './view';
const select = state => ({
channels: selectMyChannelClaims(state),
fetchingChannels: selectFetchingMyChannels(state),
youtubeChannels: selectYoutubeChannels(state),
});
const perform = dispatch => ({
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
});
export default connect(
select,
perform
)(ChannelsPage);

View file

@ -0,0 +1,48 @@
// @flow
import React, { useEffect } from 'react';
import ClaimList from 'component/claimList';
import Page from 'component/page';
import Button from 'component/button';
import YoutubeTransferStatus from 'component/youtubeTransferStatus';
type Props = {
channels: Array<ChannelClaim>,
fetchChannelListMine: () => void,
fetchingChannels: boolean,
youtubeChannels: ?Array<any>,
};
export default function ChannelsPage(props: Props) {
const { channels, fetchChannelListMine, fetchingChannels, youtubeChannels } = props;
const hasYoutubeChannels = youtubeChannels && Boolean(youtubeChannels.length);
useEffect(() => {
fetchChannelListMine();
}, [fetchChannelListMine]);
return (
<Page>
{hasYoutubeChannels && <YoutubeTransferStatus />}
{channels && channels.length ? (
<div className="card">
<ClaimList
header={__('Your Channels On LBRY')}
loading={fetchingChannels}
uris={channels.map(channel => channel.permanent_url)}
/>
</div>
) : (
<section className="main--empty">
<div className=" section--small">
<h2 className="section__title--large">{__('No Channels Created Yet')}</h2>
<div className="section__actions">
<Button button="primary" navigate="/$/publish" label={__('Create A Channel')} />
</div>
</div>
</section>
)}
</Page>
);
}

View file

@ -1,8 +1,13 @@
import { connect } from 'react-redux';
import * as SETTINGS from 'constants/settings';
import { doClearCache, doNotifyEncryptWallet, doNotifyDecryptWallet } from 'redux/actions/app';
import { doSetDaemonSetting, doSetClientSetting, doGetThemes, doSetDarkTime } from 'redux/actions/settings';
import { selectIsPasswordSaved } from 'redux/selectors/app';
import { doClearCache, doNotifyEncryptWallet, doNotifyDecryptWallet, doNotifyForgetPassword } from 'redux/actions/app';
import {
doSetDaemonSetting,
doSetClientSetting,
doGetThemes,
doChangeLanguage,
doSetDarkTime,
} from 'redux/actions/settings';
import { doSetPlayingUri } from 'redux/actions/content';
import { makeSelectClientSetting, selectDaemonSettings, selectosNotificationsEnabled } from 'redux/selectors/settings';
import { doWalletStatus, selectWalletIsEncrypted, selectBlockedChannelsCount } from 'lbry-redux';

View file

@ -11,7 +11,6 @@ import I18nMessage from 'component/i18nMessage';
import Page from 'component/page';
import SettingLanguage from 'component/settingLanguage';
import FileSelector from 'component/common/file-selector';
import WalletSecurityAndSync from '../../component/walletSecurityAndSync';
import { getSavedPassword } from 'util/saved-passwords';
type Price = {
@ -62,7 +61,7 @@ type Props = {
supportOption: boolean,
userBlockedChannelsCount?: number,
hideBalance: boolean,
confirmForgetPassword: () => void,
confirmForgetPassword: ({}) => void,
floatingPlayer: boolean,
clearPlayingUri: () => void,
darkModeTimes: DarkModeTimes,
@ -150,7 +149,11 @@ class SettingsPage extends React.PureComponent<Props, State> {
onConfirmForgetPassword() {
const { confirmForgetPassword } = this.props;
confirmForgetPassword();
confirmForgetPassword({
callback: () => {
this.setState({ storedPassword: false });
},
});
}
onChangeTime(event: SyntheticInputEvent<*>, options: OptionTimes) {
@ -245,7 +248,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
<p className="help">{__('LBRY downloads will be saved here.')}</p>
</div>
</section>
<WalletSecurityAndSync />
<section className="card card--section">
<h2 className="card__title">{__('Network and Data Settings')}</h2>
<Form>
@ -277,9 +279,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
</Form>
</section>
<section className="card card--section">
<header className="card__header">
<h2 className="card__title">{__('Max Purchase Price')}</h2>
</header>
<h2 className="card__title">{__('Max Purchase Price')}</h2>
<Form>
<FormField
@ -514,16 +514,18 @@ class SettingsPage extends React.PureComponent<Props, State> {
</React.Fragment>
}
/>
{this.state.storedPassword && (
<p className="card__subtitle card__help">
{__('Your password is saved in your OS keychain.')}{' '}
<Button
button="link"
label={__('I want to type it manually')}
onClick={this.onConfirmForgetPassword}
/>
</p>
{walletEncrypted && this.state.storedPassword && (
<FormField
type="checkbox"
name="save_password"
onChange={this.onConfirmForgetPassword}
checked={this.state.storedPassword}
label={__('Save Password')}
helper={<React.Fragment>{__('Automatically unlock your wallet on startup')}</React.Fragment>}
/>
)}
<FormField
type="checkbox"
name="hide_balance"

View file

@ -312,9 +312,9 @@ export function doNotifyUnlockWallet() {
};
}
export function doNotifyForgetPassword() {
export function doNotifyForgetPassword(props) {
return dispatch => {
dispatch(doOpenModal(MODALS.WALLET_PASSWORD_UNSAVE));
dispatch(doOpenModal(MODALS.WALLET_PASSWORD_UNSAVE, props));
};
}

View file

@ -12,6 +12,7 @@ const defaultState = {
// UX
[SETTINGS.NEW_USER_ACKNOWLEDGED]: false,
[SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED]: false,
[SETTINGS.ENABLE_SYNC]: true,
// UI
[SETTINGS.LANGUAGE]: window.localStorage.getItem(SETTINGS.LANGUAGE) || 'en',

View file

@ -23,7 +23,6 @@
&:disabled {
opacity: 0.5;
color: white !important;
}
}

View file

@ -61,13 +61,27 @@
margin-bottom: 0;
}
.card--claim-preview-wrap {
@extend .card;
margin: var(--spacing-xlarge) 0;
}
.card--claim-preview-selected {
background-color: rgba($lbry-teal-1, 0.1);
&:hover {
transition: transform 0.2s ease-in-out;
transform: scale(1.1);
}
}
// C A R D
// A C T I O N S
.card__actions {
display: flex;
align-items: center;
margin-top: var(--spacing-medium);
margin-top: var(--spacing-large);
font-size: var(--font-body);
&:only-child {

View file

@ -91,7 +91,6 @@ $border-color--dark: var(--dm-color-04);
padding: var(--spacing-medium);
&:not(.claim-preview--inline) {
@include mediaThumbHoverZoom;
cursor: pointer;
}
@ -102,12 +101,6 @@ $border-color--dark: var(--dm-color-04);
}
}
.claim-preview--injected {
padding: var(--spacing-medium);
position: relative;
}
.claim-preview--injected,
.claim-preview {
border-top: 1px solid $border-color;

View file

@ -13,7 +13,7 @@ form {
.button--inverse {
&:not(:hover),
&:hover {
// @extend .button--inverse;
@extend .button--inverse;
}
}
}

View file

@ -66,7 +66,7 @@
}
.main--contained {
max-width: 35rem;
max-width: 40rem;
min-width: 25rem;
margin: auto;
margin-top: 5rem;

View file

@ -3,7 +3,7 @@
height: 100vh;
align-items: center;
background-color: var(--color-background);
background-color: var(--color-background--splash);
display: flex;
flex-direction: column;
justify-content: center;

View file

@ -10,8 +10,16 @@
margin-bottom: var(--spacing-main-padding);
}
.section--padded {
padding: var(--spacing-large);
}
.section--small {
max-width: 30rem;
max-width: 35rem;
}
.section__header {
margin-bottom: var(--spacing-large);
}
.section__flex {

View file

@ -293,3 +293,7 @@ radio-toggle,
border-color: var(--dm-color-04);
}
}
.rc-progress-line-path {
stroke: $lbry-teal-3;
}

View file

@ -1,49 +1,47 @@
import { ipcRenderer } from 'electron';
export const setSavedPassword = value => {
return new Promise(
resolve => {
ipcRenderer.once('set-password-response', (event, success) => {
resolve(success);
});
ipcRenderer.send('set-password', value);
},
reject => {
reject(false);
}
);
return new Promise(resolve => {
ipcRenderer.once('set-password-response', (event, success) => {
resolve(success);
});
ipcRenderer.send('set-password', value);
});
};
export const getSavedPassword = () => {
return new Promise(
resolve => {
ipcRenderer.once('get-password-response', (event, password) => {
resolve(password);
});
ipcRenderer.send('get-password');
},
reject => reject(false)
);
return new Promise(resolve => {
ipcRenderer.once('get-password-response', (event, password) => {
resolve(password);
});
ipcRenderer.send('get-password');
});
};
export const deleteSavedPassword = () => {
return new Promise(resolve => {
// @if TARGET='app'
ipcRenderer.once('delete-password-response', (event, success) => {
resolve();
});
ipcRenderer.send('delete-password');
// @endif;
});
};
export const deleteAuthToken = () => {
return new Promise(
resolve => {
// @if TARGET='app'
ipcRenderer.once('delete-auth-token-response', (event, success) => {
resolve();
});
ipcRenderer.send('delete-auth-token');
// @endif;
// @if TARGET='web'
document.cookie = 'auth_token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT';
return new Promise(resolve => {
// @if TARGET='app'
ipcRenderer.once('delete-auth-token-response', (event, success) => {
resolve();
// @endif
},
reject => {
reject(false);
}
);
});
ipcRenderer.send('delete-auth-token');
// @endif;
// @if TARGET='web'
document.cookie = 'auth_token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT';
resolve();
// @endif
});
};
export const testKeychain = () => {

View file

@ -701,14 +701,8 @@
"Get ??? LBC": "Get ??? LBC",
"LBRY names cannot contain spaces or reserved symbols ($#@;/\"<>%{}|^~[]`)": "LBRY names cannot contain spaces or reserved symbols ($#@;/\"<>%{}|^~[]`)",
"Creating channel...": "Creating channel...",
"From": "From",
"To": "To",
"Multi-language support is brand new and incomplete. Switching your language may have unintended consequences, like glossolalia.": "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences, like glossolalia.",
"discovery": "discovery",
"This will add a Support button along side tipping. Similar to tips, supports help %discovery_link% but the LBC is returned to your wallet if revoked. Both also help secure your %vanity_names_link%.": "This will add a Support button along side tipping. Similar to tips, supports help %discovery_link% but the LBC is returned to your wallet if revoked. Both also help secure your %vanity_names_link%.",
"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.",
"Remember Password": "Remember Password",
"You will no longer see this at startup": "You will no longer see this at startup",
"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.",
"Add Email": "Add Email",

View file

@ -6850,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#04ae0913a444abac200731c7ed53796d763a0bbb:
lbry-redux@lbryio/lbry-redux#d44cd9ca56dee784dba42c0cc13061ae75cbd46c:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/04ae0913a444abac200731c7ed53796d763a0bbb"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/d44cd9ca56dee784dba42c0cc13061ae75cbd46c"
dependencies:
proxy-polyfill "0.1.6"
reselect "^3.0.0"
uuid "^3.3.2"
lbryinc@lbryio/lbryinc#d99232ebc754a49649a2ff4132478415faef08e2:
lbryinc@lbryio/lbryinc#368040d64658cf2a4b8a7a6725ec1787329ce65d:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/d99232ebc754a49649a2ff4132478415faef08e2"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/368040d64658cf2a4b8a7a6725ec1787329ce65d"
dependencies:
reselect "^3.0.0"
@ -9660,6 +9660,13 @@ react-compound-slider@^1.2.2:
prop-types "^15.7.2"
warning "^3.0.0"
react-confetti@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-4.0.1.tgz#f9e76b198ce02f1c13809a1d1ec1bc92f5450dde"
integrity sha512-uQrb1Q4p8Wg3xyxSGtsIxdd+hOd3jRNpVq5qET6m9B+fihsjF7mHbMngoiziya3DZtstaqCBPpTcyByXLu8CnQ==
dependencies:
tween-functions "^1.2.0"
react-dom@^16.8.2, react-dom@^16.8.6:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f"
@ -11696,6 +11703,11 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
tween-functions@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/tween-functions/-/tween-functions-1.2.0.tgz#1ae3a50e7c60bb3def774eac707acbca73bbc3ff"
integrity sha1-GuOlDnxguz3vd06scHrLynO7w/8=
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"