add createChannel to first run flow

This commit is contained in:
Sean Yesmunt 2019-08-27 10:43:42 -04:00
parent 3311e1af1f
commit 30d396fce9
28 changed files with 589 additions and 254 deletions

24
flow-typed/user.js vendored Normal file
View 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>,
};

View file

@ -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>
<Router />
<ModalRouter />
<FileViewer pageUri={uri} />

View file

@ -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;

View file

@ -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),

View file

@ -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,115 +71,161 @@ 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">
{/* @if TARGET='app' */}
{!minimal && (
<div className="header__navigation-arrows">
<Button
className="header__navigation-item header__navigation-item--back"
description={__('Navigate back')}
onClick={() => history.goBack()}
icon={ICONS.ARROW_LEFT}
iconSize={18}
/>
<Button
className="header__navigation-item header__navigation-item--forward"
description={__('Navigate forward')}
onClick={() => history.goForward()}
icon={ICONS.ARROW_RIGHT}
iconSize={18}
/>
</div>
)}
{/* @endif */}
<Button
className="header__navigation-item header__navigation-item--lbry"
label={__('LBRY')}
icon={ICONS.LBRY}
navigate="/"
/>
{/* @if TARGET='app' */}
<div className="header__navigation-arrows">
<Button
className="header__navigation-item header__navigation-item--back"
description={__('Navigate back')}
onClick={() => history.goBack()}
icon={ICONS.ARROW_LEFT}
iconSize={18}
/>
<Button
className="header__navigation-item header__navigation-item--forward"
description={__('Navigate forward')}
onClick={() => history.goForward()}
icon={ICONS.ARROW_RIGHT}
iconSize={18}
/>
{!minimal && <WunderBar />}
</div>
{!minimal ? (
<div className="header__menu">
{showFullHeader ? (
<Fragment>
<Menu>
<MenuButton className="header__navigation-item menu__title">
{roundedBalance} <LbcSymbol />
</MenuButton>
<MenuList className="menu__list--header">
<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>
</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>
<Menu>
<MenuButton className="header__navigation-item menu__title">
<Icon size={18} icon={ICONS.SETTINGS} />
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/settings`)}>
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
{__('Settings')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/help`)}>
<Icon aria-hidden icon={ICONS.HELP} />
{__('Help')}
</MenuItem>
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
{currentTheme === 'light' ? 'Dark' : 'Light'}
</MenuItem>
</MenuList>
</Menu>
</Fragment>
) : (
<Fragment>
<span />
<Button navigate={`/$/${PAGES.AUTH}/signin`} button="primary" label={__('Sign In')} />
</Fragment>
)}
</div>
{/* @endif */}
<WunderBar />
</div>
<div className="header__menu">
{isAuthenticated ? (
<Fragment>
<Menu>
<MenuButton className="header__navigation-item menu__title">
<Icon icon={ICONS.ACCOUNT} />
{getAccountTitle()}
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/account`)}>
<Icon aria-hidden icon={ICONS.OVERVIEW} />
{__('Overview')}
</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')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}>
<Icon aria-hidden icon={ICONS.PUBLISH} />
{__('Publish')}
</MenuItem>
<MenuItem className="menu__link" onSelect={signOut}>
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
{__('Sign Out')}
</MenuItem>
</MenuList>
</Menu>
<Menu>
<MenuButton className="header__navigation-item menu__title">
<Icon size={18} icon={ICONS.SETTINGS} />
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/settings`)}>
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
{__('Settings')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/help`)}>
<Icon aria-hidden icon={ICONS.HELP} />
{__('Help')}
</MenuItem>
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
{currentTheme === 'light' ? 'Dark' : 'Light'}
</MenuItem>
</MenuList>
</Menu>
</Fragment>
) : (
<Fragment>
<Button navigate={`/$/${PAGES.AUTH}/signin`} button="link" label={__('Sign In')} />
<Button navigate={`/$/${PAGES.AUTH}/signup`} button="primary" label={__('Sign Up')} />
</Fragment>
)}
</div>
) : (
<div className="header__menu">
<Tooltip label={__('Go Back')}>
<Button icon={ICONS.REMOVE} onClick={() => history.goBack()} />
</Tooltip>
</div>
)}
</div>
</header>
);

View file

@ -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;

View file

@ -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} />

View file

@ -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,34 +42,47 @@ 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>
<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>
{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}>
<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" />

View file

@ -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 (
<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)}
/>
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}>
<FormField
type="email"
label="Email"
placeholder="youremail@example.org"
name="email"
value={this.state.email}
error={errorMessage}
onChange={this.handleEmailChanged}
inputButton={
<Button type="submit" button="inverse" label="Submit" disabled={isPending || !this.state.email} />
}
/>
</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>
);
}
<Button button="primary" type="submit" label={__('Continue')} disabled={isPending} />
</Form>
);
}
export default UserEmailNew;

View file

@ -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;

View file

@ -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 />

View 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);

View 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;

View 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);

View 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;

View file

@ -50,11 +50,10 @@ class UserVerify extends React.PureComponent<Props> {
button="inverse"
label={__('Submit Phone Number')}
/>
<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>
<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>

View file

@ -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>
);

View file

@ -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>
)
);

View 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);

View 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>
);
}

View file

@ -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;

View file

@ -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,

View file

@ -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 {

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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",