Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bring in email preferences page from lbry.com #4409

Merged
merged 2 commits into from
Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions ui/component/router/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as PAGES from 'constants/pages';
import React, { useEffect } from 'react';
import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
import SettingsPage from 'page/settings';
import SettingsNotificationsPage from 'page/settingsNotifications';
import HelpPage from 'page/help';
// @if TARGET='app'
import BackupPage from 'page/backup';
Expand Down Expand Up @@ -182,6 +183,7 @@ function AppRouter(props: Props) {
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
<Route path={`/$/${PAGES.TOP}`} exact component={TopPage} />
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
<Route path={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`} exact component={SettingsNotificationsPage} />
<Route path={`/$/${PAGES.INVITE}/:referrer`} exact component={InvitedPage} />
<Route path={`/$/${PAGES.CHECKOUT}`} exact component={CheckoutPage} />

Expand Down
1 change: 1 addition & 0 deletions ui/constants/pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ exports.REWARDS = 'rewards';
exports.REWARDS_VERIFY = 'rewards/verify';
exports.SEND = 'send';
exports.SETTINGS = 'settings';
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
exports.SHOW = 'show';
exports.ACCOUNT = 'account';
exports.SEARCH = 'search';
Expand Down
2 changes: 0 additions & 2 deletions ui/page/settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
makeSelectClientSetting,
selectDaemonSettings,
selectFfmpegStatus,
selectosNotificationsEnabled,
selectFindingFFmpeg,
} from 'redux/selectors/settings';
import { doWalletStatus, selectWalletIsEncrypted, selectBlockedChannelsCount, SETTINGS } from 'lbry-redux';
Expand All @@ -39,7 +38,6 @@ const select = state => ({
automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state),
autoplay: makeSelectClientSetting(SETTINGS.AUTOPLAY)(state),
walletEncrypted: selectWalletIsEncrypted(state),
osNotificationsEnabled: selectosNotificationsEnabled(state),
autoDownload: makeSelectClientSetting(SETTINGS.AUTO_DOWNLOAD)(state),
userBlockedChannelsCount: selectBlockedChannelsCount(state),
hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state),
Expand Down
75 changes: 39 additions & 36 deletions ui/page/settings/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import * as PAGES from 'constants/pages';
import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons';
import * as React from 'react';

import { FormField, FormFieldPrice } from 'component/common/form';
Expand Down Expand Up @@ -74,7 +75,6 @@ type Props = {
decryptWallet: () => void,
updateWalletStatus: () => void,
walletEncrypted: boolean,
osNotificationsEnabled: boolean,
userBlockedChannelsCount?: number,
hideBalance: boolean,
confirmForgetPassword: ({}) => void,
Expand Down Expand Up @@ -233,7 +233,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
automaticDarkModeEnabled,
autoplay,
walletEncrypted,
osNotificationsEnabled,
// autoDownload,
setDaemonSetting,
setClientSetting,
Expand Down Expand Up @@ -481,43 +480,47 @@ class SettingsPage extends React.PureComponent<Props, State> {
/>

{(isAuthenticated || !IS_WEB) && (
<Card
title={__('Blocked Channels')}
actions={
<p>
<React.Fragment>
{userBlockedChannelsCount === 0
? __("You don't have blocked channels.")
: userBlockedChannelsCount === 1
? __('You have one blocked channel.') + ' '
: __('You have %channels% blocked channels.', { channels: userBlockedChannelsCount }) + ' '}
{
<Button
button="link"
label={userBlockedChannelsCount === 0 ? null : __('Manage')}
navigate={`/$/${PAGES.BLOCKED}`}
/>
}
</React.Fragment>
</p>
}
/>
<>
<Card
title={__('Notifications')}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could introduce tabs to settings instead of this? (not blocking feedback, can evolve)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. This definitely a step towards some tabbed/nested setting page.

actions={
<div className="section__actions">
<Button
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
disabled={userBlockedChannelsCount === 0}
navigate={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`}
/>
</div>
}
/>

<Card
title={__('Blocked Channels')}
subtitle={
userBlockedChannelsCount === 0
? __("You don't have blocked channels.")
: userBlockedChannelsCount === 1
? __('You have one blocked channel.')
: __('You have %channels% blocked channels.', { channels: userBlockedChannelsCount })
}
actions={
<div className="section__actions">
<Button
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
disabled={userBlockedChannelsCount === 0}
navigate={`/$/${PAGES.BLOCKED}`}
/>
</div>
}
/>
</>
)}

{/* @if TARGET='app' */}
<Card
title={__('Notifications')}
actions={
<FormField
type="checkbox"
name="desktopNotification"
onChange={() => setClientSetting(SETTINGS.OS_NOTIFICATIONS_ENABLED, !osNotificationsEnabled)}
checked={osNotificationsEnabled}
label={__('Show Desktop Notifications')}
helper={__('Get notified when a publish is confirmed, or when new content is available to watch.')}
/>
}
/>
<Card
title={__('Share Usage and Diagnostic Data')}
subtitle={
Expand Down
16 changes: 16 additions & 0 deletions ui/page/settingsNotifications/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import { doSetClientSetting } from 'redux/actions/settings';
import { selectosNotificationsEnabled } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import SettingsPage from './view';

const select = state => ({
osNotificationsEnabled: selectosNotificationsEnabled(state),
isAuthenticated: selectUserVerifiedEmail(state),
});

const perform = dispatch => ({
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
});

export default connect(select, perform)(SettingsPage);
185 changes: 185 additions & 0 deletions ui/page/settingsNotifications/view.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// @flow
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import * as SETTINGS from 'constants/settings';
import * as React from 'react';

import Page from 'component/page';
import { FormField } from 'component/common/form';
import Card from 'component/common/card';
import { Lbryio } from 'lbryinc';
import { useHistory } from 'react-router';
import { Redirect } from 'react-router-dom';
import Yrbl from 'component/yrbl';
import Button from 'component/button';

type Props = {
osNotificationsEnabled: boolean,
isAuthenticated: boolean,
setClientSetting: (string, boolean) => void,
};

export default function NotificationSettingsPage(props: Props) {
const { osNotificationsEnabled, setClientSetting, isAuthenticated } = props;
const [error, setError] = React.useState();
const [tagMap, setTagMap] = React.useState({});
const [tags, setTags] = React.useState();
const [enabledEmails, setEnabledEmails] = React.useState();
const { location } = useHistory();
const urlParams = new URLSearchParams(location.search);
const verificationToken = urlParams.get('verification_token');
const lbryIoParams = verificationToken ? { auth_token: verificationToken } : undefined;

React.useEffect(() => {
Lbryio.call('tag', 'list', lbryIoParams)
.then(setTags)
.catch(e => {
setError(true);
});

Lbryio.call('user_email', 'status', lbryIoParams)
.then(res => {
const enabledEmails =
res.emails &&
Object.keys(res.emails).reduce((acc, email) => {
const isEnabled = res.emails[email];
return [...acc, { email, isEnabled }];
}, []);

setTagMap(res.tags);
setEnabledEmails(enabledEmails);
})
.catch(e => {
setError(true);
});
}, []);

function handleChangeTag(name, newIsEnabled) {
const tagParams = newIsEnabled ? { add: name } : { remove: name };

Lbryio.call('user_tag', 'edit', { ...lbryIoParams, ...tagParams }).then(() => {
const newTagMap = { ...tagMap };
newTagMap[name] = newIsEnabled;

setTagMap(newTagMap);
});
}

function handleChangeEmail(email, newIsEnabled) {
Lbryio.call('user_email', 'edit', {
email: email,
enabled: newIsEnabled,
...lbryIoParams,
})
.then(() => {
const newEnabledEmails = enabledEmails
? enabledEmails.map(userEmail => {
if (email === userEmail.email) {
return { email, isEnabled: newIsEnabled };
}

return userEmail;
})
: [];

setEnabledEmails(newEnabledEmails);
})
.catch(e => {
setError(true);
});
}

if (!isAuthenticated && !verificationToken) {
return <Redirect to={`/$/${PAGES.AUTH_SIGNIN}?redirect=${location.pathname}`} />;
}

return (
<Page>
{error ? (
<Yrbl
type="sad"
title={__('Uh Oh')}
subtitle={
<>
<div>{__('There was an error displaying this page.')}</div>
<div className="section__actions">
<Button
button="secondary"
label={__('Refresh')}
icon={ICONS.REFRESH}
onClick={() => window.location.reload()}
/>
<Button button="secondary" label={__('Go Home')} icon={ICONS.HOME} navigate={'/'} />
</div>
</>
}
/>
) : (
<div className="card-stack">
{/* @if TARGET='app' */}
<Card
title={__('App Notifications')}
subtitle={__('Notification settings for the desktop app.')}
actions={
<FormField
type="checkbox"
name="desktopNotification"
onChange={() => setClientSetting(SETTINGS.OS_NOTIFICATIONS_ENABLED, !osNotificationsEnabled)}
checked={osNotificationsEnabled}
label={__('Show Desktop Notifications')}
helper={__('Get notified when a publish or channel is confirmed.')}
/>
}
/>

{/* @endif */}

{enabledEmails && enabledEmails.length > 0 && (
<Card
title={enabledEmails.length === 1 ? __('Your Email') : __('Receiving Addresses')}
subtitle={__('Uncheck your email below if you want to stop receiving messages.')}
actions={
<>
{enabledEmails.map(({ email, isEnabled }) => (
<FormField
type="checkbox"
name={`active-email:${email}`}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this safe for all potential emails?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. It's just used for the input label for screen readers

key={email}
onChange={() => handleChangeEmail(email, !isEnabled)}
checked={isEnabled}
label={email}
/>
))}
</>
}
/>
)}

{tags && tags.length > 0 && (
<Card
title={__('Email Preferences')}
subtitle={__("Opt out of any topics you don't want to receive email about.")}
actions={
<>
{tags.map(tag => {
const isEnabled = tagMap[tag.name];
return (
<FormField
type="checkbox"
key={tag.name}
name={tag.name}
onChange={() => handleChangeTag(tag.name, !isEnabled)}
checked={isEnabled}
label={tag.description}
/>
);
})}
</>
}
/>
)}
</div>
)}
</Page>
);
}