add nudges to sign up

This commit is contained in:
Sean Yesmunt 2020-11-10 00:21:04 -05:00
parent 35ab5b1578
commit f63de7f930
12 changed files with 192 additions and 39 deletions

View file

@ -1476,5 +1476,6 @@
"Currently winning": "Currently winning", "Currently winning": "Currently winning",
"URL can not include a space": "URL can not include a space", "URL can not include a space": "URL can not include a space",
"Creator analytics are down for maintenance. Please check back later.": "Creator analytics are down for maintenance. Please check back later.", "Creator analytics are down for maintenance. Please check back later.": "Creator analytics are down for maintenance. Please check back later.",
"Sign up to earn %lbc% for you and your favorite creators.": "Sign up to earn %lbc% for you and your favorite creators.",
"--end--": "--end--" "--end--": "--end--"
} }

View file

@ -1,11 +1,13 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import ClaimSupportButton from './view'; import { selectUser } from 'redux/selectors/user';
import { makeSelectTagInClaimOrChannelForUri } from 'lbry-redux'; import { makeSelectTagInClaimOrChannelForUri } from 'lbry-redux';
import ClaimSupportButton from './view';
const DISABLE_SUPPORT_TAG = 'disable-support'; const DISABLE_SUPPORT_TAG = 'disable-support';
const select = (state, props) => ({ const select = (state, props) => ({
disableSupport: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_SUPPORT_TAG)(state), disableSupport: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_SUPPORT_TAG)(state),
user: selectUser(state),
}); });
export default connect(select, { export default connect(select, {

View file

@ -4,29 +4,60 @@ import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import Button from 'component/button'; import Button from 'component/button';
import usePersistedState from 'effects/use-persisted-state';
type Props = { type Props = {
uri: string, uri: string,
doOpenModal: (string, {}) => void, doOpenModal: (string, {}) => void,
fileAction?: boolean, fileAction?: boolean,
disableSupport: boolean, disableSupport: boolean,
user: ?User,
}; };
export default function ClaimSupportButton(props: Props) { export default function ClaimSupportButton(props: Props) {
const { doOpenModal, uri, fileAction, disableSupport } = props; const { doOpenModal, uri, fileAction, disableSupport, user } = props;
const [showNudge, setShowNudge] = React.useState(false);
const [nudgeAcknowledged, setNudgeAcknowledged] = usePersistedState('nudge:support-acknowledge', false);
const emailVerified = user && user.has_verified_email;
React.useEffect(() => {
if (!emailVerified && !nudgeAcknowledged && fileAction) {
setShowNudge(true);
}
}, [emailVerified, nudgeAcknowledged, fileAction]);
if (disableSupport) { if (disableSupport) {
return null; return null;
} }
return ( return (
<Button <>
button={fileAction ? undefined : 'alt'} <Button
className={classnames({ 'button--file-action': fileAction })} button={fileAction ? undefined : 'alt'}
icon={ICONS.LBC} className={classnames({ 'button--file-action': fileAction, 'button--highlighted': showNudge })}
iconSize={fileAction ? 22 : undefined} icon={ICONS.LBC}
label={__('Support --[button to support a claim]--')} iconSize={fileAction ? 22 : undefined}
requiresAuth={IS_WEB} label={__('Support --[button to support a claim]--')}
title={__('Support this claim')} requiresAuth={IS_WEB}
onClick={() => doOpenModal(MODALS.SEND_TIP, { uri, isSupport: true })} title={__('Support this claim')}
/> onClick={() => doOpenModal(MODALS.SEND_TIP, { uri, isSupport: true })}
/>
{showNudge && (
<div className="nudge">
<div className="nudge__wrapper">
<span className="nudge__text">{__('Create an account to support this creator!')}</span>
<Button
className="nudge__close"
button="close"
icon={ICONS.REMOVE}
onClick={() => {
setNudgeAcknowledged(true);
setShowNudge(false);
}}
/>
</div>
</div>
)}
</>
); );
} }

View file

@ -174,6 +174,13 @@ const Header = (props: Props) => {
return hideBalance || Number(roundedBalance) === 0 ? __('Your Wallet') : roundedBalance; return hideBalance || Number(roundedBalance) === 0 ? __('Your Wallet') : roundedBalance;
} }
const loginButtons = (
<div className="header__auth-buttons">
<Button navigate={`/$/${PAGES.AUTH_SIGNIN}`} button="link" label={__('Log In')} className="mobile-hidden" />
<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Sign Up')} />
</div>
);
return ( return (
<header <header
className={classnames('header', { className={classnames('header', {
@ -198,18 +205,22 @@ const Header = (props: Props) => {
icon={ICONS.ARROW_LEFT} icon={ICONS.ARROW_LEFT}
/> />
{backTitle && <h1 className="header__auth-title">{isMobile ? simpleBackTitle || backTitle : backTitle}</h1>} {backTitle && <h1 className="header__auth-title">{isMobile ? simpleBackTitle || backTitle : backTitle}</h1>}
<Button {authenticated ? (
aria-label={__('Your wallet')} <Button
navigate={`/$/${PAGES.WALLET}`} aria-label={__('Your wallet')}
className="header__navigation-item menu__title header__navigation-item--balance" navigate={`/$/${PAGES.WALLET}`}
label={getWalletTitle()} className="header__navigation-item menu__title header__navigation-item--balance"
icon={ICONS.LBC} label={getWalletTitle()}
// @if TARGET='app' icon={ICONS.LBC}
onDoubleClick={e => { // @if TARGET='app'
e.stopPropagation(); onDoubleClick={e => {
}} e.stopPropagation();
// @endif }}
/> // @endif
/>
) : (
loginButtons
)}
</div> </div>
) : ( ) : (
<> <>
@ -409,17 +420,7 @@ const Header = (props: Props) => {
/> />
)} )}
{IS_WEB && !authenticated && ( {IS_WEB && !authenticated && loginButtons}
<div className="header__auth-buttons">
<Button
navigate={`/$/${PAGES.AUTH_SIGNIN}`}
button="link"
label={__('Log In')}
className="mobile-hidden"
/>
<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Sign Up')} />
</div>
)}
</div> </div>
) : ( ) : (
!isVerifyPage && !isVerifyPage &&

View file

@ -5,7 +5,9 @@ import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import classnames from 'classnames'; import classnames from 'classnames';
import Icon from 'component/common/icon';
import NotificationBubble from 'component/notificationBubble'; import NotificationBubble from 'component/notificationBubble';
import I18nMessage from 'component/i18nMessage';
import { PINNED_LABEL_1, PINNED_URI_1, PINNED_URI_2, PINNED_LABEL_2 } from 'config'; import { PINNED_LABEL_1, PINNED_URI_1, PINNED_URI_2, PINNED_LABEL_2 } from 'config';
// @if TARGET='app' // @if TARGET='app'
import { IS_MAC } from 'component/app/view'; import { IS_MAC } from 'component/app/view';
@ -260,6 +262,17 @@ function SideNavigation(props: Props) {
return () => window.removeEventListener('keydown', handleKeydown); return () => window.removeEventListener('keydown', handleKeydown);
}, [sidebarOpen, setSidebarOpen, isAbsolute]); }, [sidebarOpen, setSidebarOpen, isAbsolute]);
const unAuthNudge = (
<div className="navigation__auth-nudge">
<span>
<I18nMessage tokens={{ lbc: <Icon icon={ICONS.LBC} /> }}>
Sign up to earn %lbc% for you and your favorite creators.
</I18nMessage>
</span>
<Button button="secondary" label={__('Sign Up')} navigate={`/$/${PAGES.AUTH}`} />
</div>
);
return ( return (
<div <div
className={classnames('navigation__wrapper', { className={classnames('navigation__wrapper', {
@ -321,6 +334,20 @@ function SideNavigation(props: Props) {
))} ))}
</ul> </ul>
)} )}
{!isAuthenticated &&
(sidebarOpen ? (
unAuthNudge
) : (
<div className="navigation-links--micro">
<Button
label={__('Sign In')}
icon={ICONS.SIGN_IN}
className={classnames('navigation-link')}
activeClass="navigation-link--active"
/>
</div>
))}
</div> </div>
</nav> </nav>
)} )}
@ -396,6 +423,7 @@ function SideNavigation(props: Props) {
))} ))}
</ul> </ul>
)} )}
{!isAuthenticated && unAuthNudge}
</div> </div>
</nav> </nav>
<div <div

View file

@ -12,8 +12,10 @@ import FileSelector from 'component/common/file-selector';
import SyncToggle from 'component/syncToggle'; import SyncToggle from 'component/syncToggle';
import Card from 'component/common/card'; import Card from 'component/common/card';
import SettingAccountPassword from 'component/settingAccountPassword'; import SettingAccountPassword from 'component/settingAccountPassword';
import classnames from 'classnames';
import { getPasswordFromCookie } from 'util/saved-passwords'; import { getPasswordFromCookie } from 'util/saved-passwords';
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import Yrbl from 'component/yrbl';
type Price = { type Price = {
currency: string, currency: string,
@ -190,12 +192,27 @@ class SettingsPage extends React.PureComponent<Props, State> {
}} }}
className="card-stack" className="card-stack"
> >
{!isAuthenticated && IS_WEB && (
<div className="main--empty">
<Yrbl
type="happy"
title={__('Sign up for full control')}
subtitle={__('Unlock new buttons that change things.')}
actions={
<div className="section__actions">
<Button button="primary" icon={ICONS.SIGN_UP} label={__('Sign Up')} navigate={`/$/${PAGES.AUTH}`} />
</div>
}
/>
</div>
)}
{!IS_WEB && noDaemonSettings ? ( {!IS_WEB && noDaemonSettings ? (
<section className="card card--section"> <section className="card card--section">
<div className="card__title card__title--deprecated">{__('Failed to load settings.')}</div> <div className="card__title card__title--deprecated">{__('Failed to load settings.')}</div>
</section> </section>
) : ( ) : (
<div> <div className={classnames({ 'card--disabled': IS_WEB && !isAuthenticated })}>
<Card title={__('Language')} actions={<SettingLanguage />} /> <Card title={__('Language')} actions={<SettingLanguage />} />
{isAuthenticated && <SettingAccountPassword />} {isAuthenticated && <SettingAccountPassword />}
{/* @if TARGET='app' */} {/* @if TARGET='app' */}

View file

@ -46,7 +46,7 @@ import {
selectAllowAnalytics, selectAllowAnalytics,
} from 'redux/selectors/app'; } from 'redux/selectors/app';
import { selectDaemonSettings, makeSelectClientSetting } from 'redux/selectors/settings'; import { selectDaemonSettings, makeSelectClientSetting } from 'redux/selectors/settings';
import { selectUser } from 'redux/selectors/user'; import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
// import { selectDaemonSettings } from 'redux/selectors/settings'; // import { selectDaemonSettings } from 'redux/selectors/settings';
import { doSyncSubscribe, doSetPrefsReady } from 'redux/actions/sync'; import { doSyncSubscribe, doSetPrefsReady } from 'redux/actions/sync';
import { doAuthenticate } from 'redux/actions/user'; import { doAuthenticate } from 'redux/actions/user';
@ -335,13 +335,20 @@ export function doAlertError(errorList) {
} }
export function doAlertWaitingForSync() { export function doAlertWaitingForSync() {
return dispatch => return (dispatch, getState) => {
const state = getState();
const authenticated = selectUserVerifiedEmail(state);
dispatch( dispatch(
doToast({ doToast({
message: __('Please wait a bit, we are still getting your account ready.'), message:
!authenticated && IS_WEB
? __('Sign in or create an account to change this setting.')
: __('Please wait a bit, we are still getting your account ready.'),
isError: false, isError: false,
}) })
); );
};
} }
export function doDaemonReady() { export function doDaemonReady() {

View file

@ -37,6 +37,7 @@
@import 'component/nag'; @import 'component/nag';
@import 'component/navigation'; @import 'component/navigation';
@import 'component/notification'; @import 'component/notification';
@import 'component/nudge';
@import 'component/pagination'; @import 'component/pagination';
@import 'component/purchase'; @import 'component/purchase';
@import 'component/placeholder'; @import 'component/placeholder';

View file

@ -203,6 +203,10 @@
opacity: 0.5; opacity: 0.5;
} }
.button--highlighted {
border: 1px solid var(--color-border);
}
.button__content { .button__content {
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -236,3 +236,24 @@
top: calc(var(--header-height) + var(--mac-titlebar-height)); top: calc(var(--header-height) + var(--mac-titlebar-height));
} }
} }
.navigation__auth-nudge {
@extend .card;
margin: var(--spacing-s);
margin-top: var(--spacing-l);
padding: var(--spacing-xs);
display: flex;
flex-direction: column;
.button {
margin-top: var(--spacing-s);
}
.button__content {
justify-content: center;
}
.icon {
margin-bottom: -2px;
}
}

View file

@ -0,0 +1,39 @@
.nudge {
z-index: 3;
position: absolute;
left: 0;
top: calc(var(--height-button) + var(--spacing-s));
white-space: normal;
overflow: visible;
padding: var(--spacing-m);
border-radius: var(--border-radius);
box-shadow: var(--card-box-shadow);
background-color: var(--color-secondary);
color: var(--color-white);
.button--close {
.icon {
stroke: var(--color-white);
}
}
&::after {
content: '';
position: absolute;
height: 1rem;
width: 1rem;
top: -0.5rem;
left: 3rem;
transform: rotate(45deg);
background-color: var(--color-secondary);
}
}
.nudge__wrapper {
width: 10rem;
margin-right: var(--spacing-m);
@media (min-width: $breakpoint-small) {
width: 12.5rem;
}
}

View file

@ -104,6 +104,7 @@
} }
.section__actions { .section__actions {
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: var(--spacing-l); margin-top: var(--spacing-l);