add createChannel to first run flow
This commit is contained in:
parent
3311e1af1f
commit
30d396fce9
28 changed files with 589 additions and 254 deletions
24
flow-typed/user.js
vendored
Normal file
24
flow-typed/user.js
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
// @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>,
|
||||
};
|
|
@ -6,9 +6,12 @@ import { Lbry, buildURI, parseURI } from 'lbry-redux';
|
|||
import Router from 'component/router/index';
|
||||
import ModalRouter from 'modal/modalRouter';
|
||||
import ReactModal from 'react-modal';
|
||||
<<<<<<< HEAD
|
||||
import SideBar from 'component/sideBar';
|
||||
import Header from 'component/header';
|
||||
import Button from 'component/button';
|
||||
=======
|
||||
>>>>>>> add createChannel to first run flow
|
||||
import { openContextMenu } from 'util/context-menu';
|
||||
import useKonamiListener from 'util/enhanced-layout';
|
||||
import Yrbl from 'component/yrbl';
|
||||
|
@ -111,13 +114,7 @@ function App(props: Props) {
|
|||
|
||||
return (
|
||||
<div className={MAIN_WRAPPER_CLASS} ref={appRef} onContextMenu={e => openContextMenu(e)}>
|
||||
<Header />
|
||||
|
||||
<div className="main-wrapper__inner">
|
||||
<Router />
|
||||
<SideBar />
|
||||
</div>
|
||||
|
||||
<ModalRouter />
|
||||
<FileViewer pageUri={uri} />
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { formatCredits, formatFullPrice } from 'util/format-credits';
|
||||
import { formatCredits, formatFullPrice } from 'lbry-redux';
|
||||
|
||||
type Props = {
|
||||
amount: number,
|
||||
|
@ -39,7 +39,7 @@ class CreditAmount extends React.PureComponent<Props> {
|
|||
formattedAmount =
|
||||
amount > 0 && amount < minimumRenderableAmount
|
||||
? `<${minimumRenderableAmount}`
|
||||
: formatCredits(amount, precision);
|
||||
: formatCredits(amount, precision, true);
|
||||
}
|
||||
|
||||
let amountText;
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectBalance, SETTINGS as LBRY_REDUX_SETTINGS } from 'lbry-redux';
|
||||
import { selectBalance, formatCredits } from 'lbry-redux';
|
||||
import { selectUserEmail } from 'lbryinc';
|
||||
import { formatCredits } from 'util/format-credits';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import Header from './view';
|
||||
|
||||
const select = state => ({
|
||||
balance: selectBalance(state),
|
||||
language: makeSelectClientSetting(LBRY_REDUX_SETTINGS.LANGUAGE)(state), // trigger redraw on language change
|
||||
roundedBalance: formatCredits(selectBalance(state) || 0, 2),
|
||||
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), // trigger redraw on language change
|
||||
roundedBalance: formatCredits(selectBalance(state) || 0, 2, true),
|
||||
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
|
||||
automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state),
|
||||
hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state),
|
||||
|
|
|
@ -4,11 +4,13 @@ import * as SETTINGS from 'constants/settings';
|
|||
import * as PAGES from 'constants/pages';
|
||||
import React, { Fragment } from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import classnames from 'classnames';
|
||||
import Button from 'component/button';
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
import WunderBar from 'component/wunderbar';
|
||||
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';
|
||||
|
@ -38,6 +40,7 @@ type Props = {
|
|||
setClientSetting: (string, boolean | string) => void,
|
||||
hideBalance: boolean,
|
||||
email: ?string,
|
||||
minimal: boolean,
|
||||
};
|
||||
|
||||
const Header = (props: Props) => {
|
||||
|
@ -49,8 +52,12 @@ const Header = (props: Props) => {
|
|||
automaticDarkModeEnabled,
|
||||
hideBalance,
|
||||
email,
|
||||
minimal,
|
||||
} = 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;
|
||||
|
||||
function handleThemeToggle() {
|
||||
if (automaticDarkModeEnabled) {
|
||||
|
@ -64,35 +71,50 @@ const Header = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
function getAccountTitle() {
|
||||
if (roundedBalance > 0 && !hideBalance) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{roundedBalance} <LbcSymbol />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return __('Account');
|
||||
}
|
||||
|
||||
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'),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="header">
|
||||
<header className={classnames('header', { 'header--minimal': minimal })}>
|
||||
<div className="header__contents">
|
||||
<div className="header__navigation">
|
||||
<Button
|
||||
className="header__navigation-item header__navigation-item--lbry"
|
||||
label={__('LBRY')}
|
||||
icon={ICONS.LBRY}
|
||||
navigate="/"
|
||||
/>
|
||||
{/* @if TARGET='app' */}
|
||||
{!minimal && (
|
||||
<div className="header__navigation-arrows">
|
||||
<Button
|
||||
className="header__navigation-item header__navigation-item--back"
|
||||
|
@ -110,39 +132,63 @@ const Header = (props: Props) => {
|
|||
iconSize={18}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* @endif */}
|
||||
<WunderBar />
|
||||
|
||||
<Button
|
||||
className="header__navigation-item header__navigation-item--lbry"
|
||||
label={__('LBRY')}
|
||||
icon={ICONS.LBRY}
|
||||
navigate="/"
|
||||
/>
|
||||
|
||||
{!minimal && <WunderBar />}
|
||||
</div>
|
||||
|
||||
{!minimal ? (
|
||||
<div className="header__menu">
|
||||
{isAuthenticated ? (
|
||||
{showFullHeader ? (
|
||||
<Fragment>
|
||||
<Menu>
|
||||
<MenuButton className="header__navigation-item menu__title">
|
||||
<Icon icon={ICONS.ACCOUNT} />
|
||||
{getAccountTitle()}
|
||||
{roundedBalance} <LbcSymbol />
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/account`)}>
|
||||
<Icon aria-hidden icon={ICONS.OVERVIEW} />
|
||||
{__('Overview')}
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/wallet`)}>
|
||||
<Icon aria-hidden icon={ICONS.WALLET} />
|
||||
{__('Wallet')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/rewards`)}>
|
||||
<Icon aria-hidden icon={ICONS.FEATURED} />
|
||||
{__('Rewards')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/wallet`)}>
|
||||
<Icon aria-hidden icon={ICONS.WALLET} />
|
||||
{__('Wallet')}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
<Menu>
|
||||
<MenuButton className="header__navigation-item menu__title">
|
||||
<Icon size={18} icon={ICONS.ACCOUNT} />
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem
|
||||
className="menu__link"
|
||||
onSelect={() => history.push(isAuthenticated ? `/$/account` : `/$/auth/signup`)}
|
||||
>
|
||||
<Icon aria-hidden icon={ICONS.OVERVIEW} />
|
||||
{__('Overview')}
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}>
|
||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||
{__('Publish')}
|
||||
</MenuItem>
|
||||
{isAuthenticated ? (
|
||||
<MenuItem className="menu__link" onSelect={signOut}>
|
||||
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
||||
{__('Sign Out')}
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem onSelect={() => {}} />
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
|
@ -168,11 +214,18 @@ const Header = (props: Props) => {
|
|||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Button navigate={`/$/${PAGES.AUTH}/signin`} button="link" label={__('Sign In')} />
|
||||
<Button navigate={`/$/${PAGES.AUTH}/signup`} button="primary" label={__('Sign Up')} />
|
||||
<span />
|
||||
<Button navigate={`/$/${PAGES.AUTH}/signin`} button="primary" label={__('Sign In')} />
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="header__menu">
|
||||
<Tooltip label={__('Go Back')}>
|
||||
<Button icon={ICONS.REMOVE} onClick={() => history.goBack()} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
|
|
@ -1,16 +1,50 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
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: React.Node | Array<React.Node>,
|
||||
children: Node | Array<Node>,
|
||||
className: ?string,
|
||||
autoUpdateDownloaded: boolean,
|
||||
isUpgradeAvailable: boolean,
|
||||
fullscreen: boolean,
|
||||
doDownloadUpgradeRequested: () => void,
|
||||
};
|
||||
|
||||
function Page(props: Props) {
|
||||
const { children, className } = props;
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
fullscreen = false,
|
||||
autoUpdateDownloaded,
|
||||
isUpgradeAvailable,
|
||||
doDownloadUpgradeRequested,
|
||||
} = props;
|
||||
const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable);
|
||||
|
||||
return <main className={classnames('main', className)}>{children}</main>;
|
||||
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 />}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -21,10 +21,9 @@ import WalletPage from 'page/wallet';
|
|||
import NavigationHistory from 'page/navigationHistory';
|
||||
import TagsPage from 'page/tags';
|
||||
import FollowingPage from 'page/following';
|
||||
import ListBlocked from 'page/listBlocked';
|
||||
import ListBlockedPage from 'page/listBlocked';
|
||||
import FourOhFourPage from 'page/fourOhFour';
|
||||
|
||||
import UserEmail from 'component/userEmail';
|
||||
import SignInPage from 'page/signIn';
|
||||
|
||||
// Tell the browser we are handling scroll restoration
|
||||
if ('scrollRestoration' in history) {
|
||||
|
@ -36,14 +35,6 @@ type Props = {
|
|||
location: { pathname: string, search: string },
|
||||
};
|
||||
|
||||
function SignInPage() {
|
||||
return <UserEmail />;
|
||||
}
|
||||
|
||||
function SignUpPage() {
|
||||
return <h1>Sign In</h1>;
|
||||
}
|
||||
|
||||
function AppRouter(props: Props) {
|
||||
const { currentScroll } = props;
|
||||
|
||||
|
@ -57,7 +48,6 @@ function AppRouter(props: Props) {
|
|||
<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.AUTH}/signup`} exact component={SignUpPage} />
|
||||
<Route path={`/$/${PAGES.INVITE}`} exact component={InvitePage} />
|
||||
<Route path={`/$/${PAGES.DOWNLOADED}`} exact component={FileListDownloaded} />
|
||||
<Route path={`/$/${PAGES.PUBLISHED}`} exact component={FileListPublished} />
|
||||
|
@ -74,7 +64,7 @@ function AppRouter(props: Props) {
|
|||
<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={ListBlocked} />
|
||||
<Route path={`/$/${PAGES.BLOCKED}`} exact 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} />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import Button from 'component/button';
|
||||
import Tag from 'component/tag';
|
||||
import StickyBox from 'react-sticky-box/dist/esnext';
|
||||
|
@ -14,6 +14,7 @@ type Props = {
|
|||
|
||||
function SideBar(props: Props) {
|
||||
const { subscriptions, followedTags, email } = props;
|
||||
const showSideBar = IS_WEB ? Boolean(email) : true;
|
||||
|
||||
function buildLink(path, label, icon, guide) {
|
||||
return {
|
||||
|
@ -26,7 +27,7 @@ function SideBar(props: Props) {
|
|||
|
||||
return (
|
||||
<StickyBox offsetTop={100} offsetBottom={20}>
|
||||
{email ? (
|
||||
{showSideBar ? (
|
||||
<nav className="navigation">
|
||||
<ul className="navigation-links">
|
||||
{[
|
||||
|
@ -41,15 +42,22 @@ function SideBar(props: Props) {
|
|||
{
|
||||
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.FOLLOWING, __('Customize'), ICONS.EDIT),
|
||||
},
|
||||
].map(linkProps => (
|
||||
<li key={linkProps.label}>
|
||||
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{email ? (
|
||||
<Fragment>
|
||||
<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}>
|
||||
|
@ -69,6 +77,12 @@ function SideBar(props: Props) {
|
|||
</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" />
|
||||
|
|
|
@ -1,43 +1,23 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { FormField, Form } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import analytics from 'analytics';
|
||||
|
||||
type Props = {
|
||||
cancelButton: React.Node,
|
||||
errorMessage: ?string,
|
||||
isPending: boolean,
|
||||
addUserEmail: string => void,
|
||||
};
|
||||
|
||||
type State = {
|
||||
email: string,
|
||||
};
|
||||
function UserEmailNew(props: Props) {
|
||||
const { errorMessage, isPending, addUserEmail } = props;
|
||||
const [newEmail, setEmail] = useState('');
|
||||
const [sync, setSync] = useState(false);
|
||||
|
||||
class UserEmailNew extends React.PureComponent<Props, State> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
email: '',
|
||||
};
|
||||
|
||||
(this: any).handleSubmit = this.handleSubmit.bind(this);
|
||||
(this: any).handleEmailChanged = this.handleEmailChanged.bind(this);
|
||||
}
|
||||
|
||||
handleEmailChanged(event: SyntheticInputEvent<*>) {
|
||||
this.setState({
|
||||
email: event.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
const { email } = this.state;
|
||||
const { addUserEmail } = this.props;
|
||||
addUserEmail(email);
|
||||
function handleSubmit() {
|
||||
addUserEmail(newEmail);
|
||||
analytics.emailProvidedEvent();
|
||||
|
||||
// @if TARGET='web'
|
||||
|
@ -45,40 +25,28 @@ class UserEmailNew extends React.PureComponent<Props, State> {
|
|||
// @endif
|
||||
}
|
||||
|
||||
render() {
|
||||
const { cancelButton, errorMessage, isPending } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h2 className="card__title">{__('Verify Your Email')}</h2>
|
||||
<p className="card__subtitle">
|
||||
{/* @if TARGET='app' */}
|
||||
{__("We'll let you know about LBRY updates, security issues, and great new content.")}
|
||||
{/* @endif */}
|
||||
{/* @if TARGET='web' */}
|
||||
{__('Stay up to date with lbry.tv and be the first to know about the progress we make.')}
|
||||
{/* @endif */}
|
||||
</p>
|
||||
|
||||
<Form onSubmit={this.handleSubmit}>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormField
|
||||
type="email"
|
||||
label="Email"
|
||||
placeholder="youremail@example.org"
|
||||
name="email"
|
||||
value={this.state.email}
|
||||
id="sign_up_email"
|
||||
label={__('Email')}
|
||||
value={newEmail}
|
||||
error={errorMessage}
|
||||
onChange={this.handleEmailChanged}
|
||||
inputButton={
|
||||
<Button type="submit" button="inverse" label="Submit" disabled={isPending || !this.state.email} />
|
||||
}
|
||||
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 className="card__actions">{cancelButton}</div>
|
||||
<p className="help">{__('Your email address will never be sold and you can unsubscribe at any time.')}</p>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserEmailNew;
|
||||
|
|
|
@ -18,7 +18,7 @@ function UserEmailResetButton(props: Props) {
|
|||
}
|
||||
: { href: 'https://lbry.com/faq/how-to-change-email' };
|
||||
|
||||
return <Button button={button} label={__('Change')} {...buttonsProps} />;
|
||||
return <Button button={button} label={__('Change... fix me sean')} {...buttonsProps} />;
|
||||
}
|
||||
|
||||
export default UserEmailResetButton;
|
||||
|
|
|
@ -24,7 +24,8 @@ class UserEmailVerify extends React.PureComponent<Props> {
|
|||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.emailVerifyCheckInterval && this.props.user.has_verified_email) {
|
||||
const { user } = this.props;
|
||||
if (this.emailVerifyCheckInterval && user && user.has_verified_email) {
|
||||
clearInterval(this.emailVerifyCheckInterval);
|
||||
}
|
||||
}
|
||||
|
@ -51,8 +52,6 @@ class UserEmailVerify extends React.PureComponent<Props> {
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h2 className="card__title">{__('Waiting For Verification')}</h2>
|
||||
|
||||
<p className="card__subtitle">
|
||||
{__('An email was sent to')} {email}.{' '}
|
||||
{__('Follow the link and you will be good to go. This will update automatically.')}
|
||||
|
@ -61,7 +60,7 @@ class UserEmailVerify extends React.PureComponent<Props> {
|
|||
<div className="card__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('Resend verification email')}
|
||||
label={__('Resend Verification Email')}
|
||||
onClick={this.handleResendVerificationEmail}
|
||||
/>
|
||||
<UserEmailResetButton />
|
||||
|
|
28
src/ui/component/userFirstChannel/index.js
Normal file
28
src/ui/component/userFirstChannel/index.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
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';
|
||||
|
||||
const select = state => ({
|
||||
email: selectEmailToVerify(state),
|
||||
user: selectUser(state),
|
||||
creatingChannel: selectCreatingChannel(state),
|
||||
channels: selectMyChannelClaims(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);
|
65
src/ui/component/userFirstChannel/view.jsx
Normal file
65
src/ui/component/userFirstChannel/view.jsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
// @flow
|
||||
import React, { useState } from 'react';
|
||||
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;
|
||||
|
||||
type Props = {
|
||||
createChannel: (string, number) => void,
|
||||
claimReward: (() => void) => void,
|
||||
creatingChannel: boolean,
|
||||
};
|
||||
|
||||
function UserFirstChannel(props: Props) {
|
||||
const { createChannel, creatingChannel, claimReward } = props;
|
||||
const [channel, setChannel] = useState('');
|
||||
const [nameError, setNameError] = useState();
|
||||
|
||||
function handleCreateChannel() {
|
||||
claimReward(() => {
|
||||
createChannel(`@${channel}`, DEFAULT_BID_FOR_FIRST_CHANNEL);
|
||||
});
|
||||
}
|
||||
|
||||
function handleChannelChange(e) {
|
||||
const { value } = e.target;
|
||||
setChannel(value);
|
||||
if (!isNameValid(value, false)) {
|
||||
setNameError(__('LBRY names cannot contain spaces or reserved symbols ($#@;/"<>%{}|^~[]`)'));
|
||||
} else {
|
||||
setNameError();
|
||||
}
|
||||
}
|
||||
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserFirstChannel;
|
27
src/ui/component/userSignIn/index.js
Normal file
27
src/ui/component/userSignIn/index.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectEmailToVerify,
|
||||
doUserResendVerificationEmail,
|
||||
doUserCheckEmailVerified,
|
||||
selectUser,
|
||||
doFetchAccessToken,
|
||||
selectAccessToken,
|
||||
} from 'lbryinc';
|
||||
import UserSignUp from './view';
|
||||
|
||||
const select = state => ({
|
||||
email: selectEmailToVerify(state),
|
||||
user: selectUser(state),
|
||||
accessToken: selectAccessToken(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
|
||||
checkEmailVerified: () => dispatch(doUserCheckEmailVerified()),
|
||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(UserSignUp);
|
32
src/ui/component/userSignIn/view.jsx
Normal file
32
src/ui/component/userSignIn/view.jsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import UserEmailNew from 'component/userEmailNew';
|
||||
import UserEmailVerify from 'component/userEmailVerify';
|
||||
|
||||
type Props = {
|
||||
user: ?User,
|
||||
email: ?string,
|
||||
};
|
||||
|
||||
function UserSignUp(props: Props) {
|
||||
const { email, user } = props;
|
||||
const verifiedEmail = user && email && user.has_verified_email;
|
||||
|
||||
function getTitle() {
|
||||
if (!email) {
|
||||
return __('Get Rockin');
|
||||
} else if (email && !verifiedEmail) {
|
||||
return __('We Sent You An Email');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h1 className="card__title--large">{getTitle()}</h1>
|
||||
{!email && <UserEmailNew />}
|
||||
{email && !verifiedEmail && <UserEmailVerify />}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserSignUp;
|
|
@ -50,12 +50,11 @@ class UserVerify extends React.PureComponent<Props> {
|
|||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="card card--section">
|
||||
|
|
|
@ -8,7 +8,7 @@ import * as ACTIONS from 'constants/action_types';
|
|||
import { ipcRenderer, remote, shell } from 'electron';
|
||||
import moment from 'moment';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { Fragment, useState, useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { doConditionalAuthNavigate, doDaemonReady, doAutoUpdate, doOpenModal, doHideModal } from 'redux/actions/app';
|
||||
|
@ -237,7 +237,7 @@ function AppWrapper() {
|
|||
return (
|
||||
<Provider store={store}>
|
||||
<PersistGate persistor={persistor} loading={<div className="main--launching" />}>
|
||||
<div>
|
||||
<Fragment>
|
||||
{readyToLaunch ? (
|
||||
<ConnectedRouter history={history}>
|
||||
<ErrorBoundary>
|
||||
|
@ -251,7 +251,7 @@ function AppWrapper() {
|
|||
onReadyToLaunch={() => setReadyToLaunch(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React, { PureComponent, Fragment } from 'react';
|
||||
import BusyIndicator from 'component/common/busy-indicator';
|
||||
import RewardListClaimed from 'component/rewardListClaimed';
|
||||
|
@ -39,15 +40,18 @@ class RewardsPage extends PureComponent<Props> {
|
|||
return (
|
||||
!IS_WEB && (
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('Rewards Approval to Earn Credits (LBC)')}</h2>
|
||||
<h2 className="card__title">{__('Sign In To Unlock Rewards')}</h2>
|
||||
<p className="card__subtitle">
|
||||
{__(
|
||||
'This step is optional. You can continue to use this app without rewards, but LBC may be needed for some tasks.'
|
||||
)}{' '}
|
||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />.
|
||||
'This is optional. You can continue to use this app without rewards, but LBC may be needed for some tasks.'
|
||||
)}
|
||||
</p>
|
||||
|
||||
<Button navigate="/$/auth?redirect=rewards" button="primary" label="Prove Humanity" />
|
||||
<Button
|
||||
navigate={`/$/${PAGES.AUTH}/signin?redirect=rewards`}
|
||||
button="primary"
|
||||
label={__('Unlock Rewards')}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
);
|
||||
|
|
15
src/ui/page/signIn/index.js
Normal file
15
src/ui/page/signIn/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
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),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
)(SignUpPage);
|
42
src/ui/page/signIn/view.jsx
Normal file
42
src/ui/page/signIn/view.jsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
// @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}` : '/');
|
||||
}
|
||||
|
||||
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 />}
|
||||
</Page>
|
||||
);
|
||||
}
|
|
@ -49,6 +49,12 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
.card--fill {
|
||||
min-width: 25rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
// C A R D
|
||||
// A C T I O N S
|
||||
|
||||
|
@ -189,6 +195,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.card__title--large {
|
||||
@extend .card__title;
|
||||
font-weight: 700;
|
||||
font-size: var(--font-heading);
|
||||
}
|
||||
|
||||
.card__title--between {
|
||||
@extend .card__title;
|
||||
justify-content: space-between;
|
||||
|
|
|
@ -85,6 +85,10 @@ fieldset-section:last-child {
|
|||
|
||||
checkbox-element,
|
||||
radio-element {
|
||||
&:hover {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
label {
|
||||
color: lighten($lbry-black, 20%);
|
||||
margin-bottom: 0;
|
||||
|
@ -102,6 +106,9 @@ radio-element {
|
|||
}
|
||||
|
||||
checkbox-toggle {
|
||||
border-width: 1px;
|
||||
border-radius: var(--input-border-radius);
|
||||
|
||||
&:before {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
|
@ -155,6 +162,10 @@ fieldset-group {
|
|||
|
||||
label {
|
||||
min-height: 18px;
|
||||
white-space: nowrap;
|
||||
// Set width 0 and overflow visible so the label can act as if it's the input label and not a random text node in a side by side div
|
||||
overflow: visible;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
fieldset-section:first-child .form-field__prefix,
|
||||
|
|
|
@ -20,6 +20,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.header--minimal {
|
||||
box-shadow: none;
|
||||
background-color: transparent;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.header__contents {
|
||||
max-width: var(--page-max-width);
|
||||
height: calc(var(--header-height) - 1px);
|
||||
|
@ -47,6 +53,10 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.button {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.header__navigation-arrows {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.main-wrapper {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
min-height: 80vh;
|
||||
|
||||
[data-mode='dark'] & {
|
||||
background-color: var(--dm-color-08);
|
||||
|
@ -41,6 +41,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.main--auth-page {
|
||||
max-width: 60rem;
|
||||
margin-top: calc(var(--spacing-main-padding) * 2);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.main--empty {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
@ -56,6 +63,24 @@
|
|||
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;
|
||||
|
||||
* {
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
|
||||
.main__status {
|
||||
@extend .card__subtitle;
|
||||
display: flex;
|
||||
|
|
|
@ -89,7 +89,6 @@
|
|||
color: lighten($lbry-black, 20%);
|
||||
|
||||
.icon {
|
||||
margin-right: var(--spacing-small);
|
||||
stroke: $lbry-gray-5;
|
||||
}
|
||||
|
||||
|
@ -101,3 +100,16 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu__link {
|
||||
.icon {
|
||||
margin-right: var(--spacing-small);
|
||||
stroke: $lbry-gray-5;
|
||||
}
|
||||
}
|
||||
|
||||
.menu__title {
|
||||
span {
|
||||
margin-left: var(--spacing-miniscule);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ body {
|
|||
cursor: default;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
height: 100%;
|
||||
line-height: 1.5;
|
||||
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
||||
|
||||
|
@ -107,10 +106,13 @@ a {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 15rem;
|
||||
margin-bottom: var(--spacing-large);
|
||||
|
||||
&:first-child {
|
||||
margin-right: 1.5rem;
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
export function formatCredits(amount, precision = 1) {
|
||||
return parseFloat(amount)
|
||||
.toFixed(precision || 1)
|
||||
.replace(/\.?0+$/, '');
|
||||
}
|
||||
|
||||
export function formatFullPrice(amount, precision = 1) {
|
||||
if (!amount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let formated = '';
|
||||
const quantity = amount.toString().split('.');
|
||||
const fraction = quantity[1];
|
||||
|
||||
if (fraction) {
|
||||
const decimals = fraction.split('');
|
||||
const first = decimals.filter(number => number !== '0')[0];
|
||||
const index = decimals.indexOf(first);
|
||||
|
||||
// Set format fraction
|
||||
formated = `.${fraction.substring(0, index + precision)}`;
|
||||
}
|
||||
|
||||
return parseFloat(quantity[0] + formated);
|
||||
}
|
|
@ -638,7 +638,6 @@
|
|||
"Refreshed!": "Refreshed!",
|
||||
"Starting...": "Starting...",
|
||||
"Spin Spin Sugar": "Spin Spin Sugar",
|
||||
"URI does not include name.": "URI does not include name.",
|
||||
"You're not following any channels.": "You're not following any channels.",
|
||||
"Look what's trending for everyone": "Look what's trending for everyone",
|
||||
"or": "or",
|
||||
|
|
Loading…
Reference in a new issue