Convert setting sub-pages to new style

"Creator Settings", "Blocked and Muted", "Manage notifications", "Stripe"
This commit is contained in:
infinite-persistence 2021-08-19 13:58:40 +08:00
parent 1671deb0b2
commit e1223d0d02
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
8 changed files with 319 additions and 254 deletions

View file

@ -1744,8 +1744,9 @@
"Delegation": "Delegation",
"Add moderator": "Add moderator",
"Enter a @username or URL": "Enter a @username or URL",
"examples: @channel, @channel#3, https://odysee.com/@Odysee:8, lbry://@Odysee#8": "examples: @channel, @channel#3, https://odysee.com/@Odysee:8, lbry://@Odysee#8",
"Enter a channel name or URL to add as a moderator.\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8": "Enter a channel name or URL to add as a moderator.\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8",
"Moderators": "Moderators",
"Moderators can block channels on your behalf. Blocked channels will appear in your \"Blocked and Muted\" list.": "Moderators can block channels on your behalf. Blocked channels will appear in your \"Blocked and Muted\" list.",
"Add as moderator": "Add as moderator",
"Mute (m)": "Mute (m)",
"Playback Rate (<, >)": "Playback Rate (<, >)",
@ -1934,6 +1935,7 @@
"Clarification": "Clarification",
"Client name": "Client name",
"Creator settings": "Creator settings",
"Comments and livestream chat containing these words will be blocked.": "Comments and livestream chat containing these words will be blocked.",
"Muted words": "Muted words",
"Add words": "Add words",
"Suggestions": "Suggestions",

View file

@ -229,7 +229,12 @@ function ListBlocked(props: Props) {
}, [stringifiedPersonalList, justPersonalBlocked, setLocalPersonalList]);
return (
<Page>
<Page
noFooter
noSideNavigation
settingsPage
backout={{ title: __('Blocked and muted channels'), backLabel: __('Back') }}
>
{fetchingModerationBlockList && (
<div className="main--empty">
<Spinner />

View file

@ -1,12 +1,15 @@
// @flow
import * as ICONS from 'constants/icons';
import * as React from 'react';
import Card from 'component/common/card';
import TagsSearch from 'component/tagsSearch';
import Page from 'component/page';
import Button from 'component/button';
import ChannelSelector from 'component/channelSelector';
import SettingsRow from 'component/settingsRow';
import Spinner from 'component/spinner';
import { FormField } from 'component/common/form-components/form-field';
import Icon from 'component/common/icon';
import LbcSymbol from 'component/common/lbc-symbol';
import I18nMessage from 'component/i18nMessage';
import { isNameValid, parseURI } from 'lbry-redux';
@ -284,194 +287,213 @@ export default function SettingsCreatorPage(props: Props) {
<Page
noFooter
noSideNavigation
backout={{
title: __('Creator settings'),
backLabel: __('Done'),
}}
settingsPage
backout={{ title: __('Creator settings'), backLabel: __('Back') }}
className="card-stack"
>
<ChannelSelector hideAnon />
{isBusy && (
<div className="main--empty">
<Spinner />
</div>
)}
{isDisabled && (
<Card
title={__('Settings unavailable for this channel')}
subtitle={__("This channel isn't staking enough LBRY Credits to enable Creator Settings.")}
/>
)}
{!isBusy && !isDisabled && (
<>
<div className="card-stack">
<ChannelSelector hideAnon />
{isBusy && (
<div className="main--empty">
<Spinner />
</div>
)}
{isDisabled && (
<Card
title={__('General')}
actions={
<>
<FormField
type="checkbox"
name="comments_enabled"
label={__('Enable comments for channel.')}
checked={commentsEnabled}
onChange={() => setSettings({ comments_enabled: !commentsEnabled })}
/>
<FormField
name="slow_mode_min_gap"
label={__('Minimum time gap in seconds between comments (affects livestream chat as well).')}
min={0}
step={1}
type="number"
placeholder="1"
value={slowModeMin}
onChange={(e) => {
const value = parseInt(e.target.value);
setSlowModeMin(value);
pushSlowModeMinDebounced(value, activeChannelClaim);
}}
onBlur={() => setLastUpdated(Date.now())}
/>
</>
}
title={__('Settings unavailable for this channel')}
subtitle={__("This channel isn't staking enough LBRY Credits to enable Creator Settings.")}
/>
<Card
title={__('Filter')}
actions={
<div className="tag--blocked-words">
<TagsSearch
label={__('Muted words')}
labelAddNew={__('Add words')}
labelSuggestions={__('Suggestions')}
onRemove={removeMutedWord}
onSelect={addMutedWords}
disableAutoFocus
tagsPassedIn={mutedWordTags}
placeholder={__('Add words to block')}
hideSuggestions
disableControlTags
/>
</div>
}
/>
<Card
title={__('Tip')}
actions={
<>
<FormField
name="min_tip_amount_comment"
label={
<I18nMessage tokens={{ lbc: <LbcSymbol /> }}>Minimum %lbc% tip amount for comments</I18nMessage>
}
helper={__(
'Enabling a minimum amount to comment will force all comments, including livestreams, to have tips associated with them. This can help prevent spam.'
)}
className="form-field--price-amount"
max={LBC_MAX}
min={LBC_MIN}
step={LBC_STEP}
type="number"
placeholder="1"
value={minTip}
onChange={(e) => {
const newMinTip = parseFloat(e.target.value);
setMinTip(newMinTip);
pushMinTipDebounced(newMinTip, activeChannelClaim);
if (newMinTip !== 0 && minSuper !== 0) {
setMinSuper(0);
pushMinSuperDebounced(0, activeChannelClaim);
}
}}
onBlur={() => setLastUpdated(Date.now())}
/>
<FormField
name="min_tip_amount_super_chat"
label={
<I18nMessage tokens={{ lbc: <LbcSymbol /> }}>Minimum %lbc% tip amount for hyperchats</I18nMessage>
}
helper={
<>
{__(
'Enabling a minimum amount to hyperchat will force all TIPPED comments to have this value in order to be shown. This still allows regular comments to be posted.'
)}
{minTip !== 0 && (
<p className="help--inline">
<em>{__('(This settings is not applicable if all comments require a tip.)')}</em>
</p>
)}
</>
}
className="form-field--price-amount"
min={0}
step="any"
type="number"
placeholder="1"
value={minSuper}
disabled={minTip !== 0}
onChange={(e) => {
const newMinSuper = parseFloat(e.target.value);
setMinSuper(newMinSuper);
pushMinSuperDebounced(newMinSuper, activeChannelClaim);
}}
onBlur={() => setLastUpdated(Date.now())}
/>
</>
}
/>
<Card
title={__('Delegation')}
className="card--enable-overflow"
actions={
<div className="tag--blocked-words">
<FormField
type="text"
name="moderator_search"
className="form-field--address"
label={__('Add moderator')}
placeholder={__('Enter a @username or URL')}
helper={__('examples: @channel, @channel#3, https://odysee.com/@Odysee:8, lbry://@Odysee#8')}
value={moderatorSearchTerm}
onChange={(e) => setModeratorSearchTerm(e.target.value)}
error={moderatorSearchError}
/>
{moderatorSearchClaimUri && (
<div className="section">
<ClaimPreview
key={moderatorSearchClaimUri}
uri={moderatorSearchClaimUri}
// type={'small'}
// showNullPlaceholder
hideMenu
hideRepostLabel
disableNavigation
properties={''}
renderActions={(claim) => {
return (
<Button
requiresAuth
button="primary"
label={__('Add as moderator')}
onClick={() => handleChannelSearchSelect(claim)}
/>
);
}}
)}
{!isBusy && !isDisabled && (
<>
<Card
isBodyList
body={
<>
<SettingsRow title={__('Enable comments for channel.')}>
<FormField
type="checkbox"
name="comments_enabled"
checked={commentsEnabled}
onChange={() => setSettings({ comments_enabled: !commentsEnabled })}
/>
</div>
)}
<TagsSearch
label={__('Moderators')}
labelAddNew={__('Add moderator')}
onRemove={removeModerator}
onSelect={addModerator}
tagsPassedIn={moderatorTags}
disableAutoFocus
hideInputField
hideSuggestions
disableControlTags
/>
</div>
}
/>
</>
)}
</SettingsRow>
<SettingsRow title={__('Slow mode')} subtitle={__(HELP.SLOW_MODE)}>
<FormField
name="slow_mode_min_gap"
min={0}
step={1}
type="number"
placeholder="1"
value={slowModeMin}
onChange={(e) => {
const value = parseInt(e.target.value);
setSlowModeMin(value);
pushSlowModeMinDebounced(value, activeChannelClaim);
}}
onBlur={() => setLastUpdated(Date.now())}
/>
</SettingsRow>
<SettingsRow
title={
<I18nMessage tokens={{ lbc: <LbcSymbol /> }}>Minimum %lbc% tip amount for comments</I18nMessage>
}
subtitle={__(HELP.MIN_TIP)}
>
<FormField
name="min_tip_amount_comment"
className="form-field--price-amount"
max={LBC_MAX}
min={LBC_MIN}
step={LBC_STEP}
type="number"
placeholder="1"
value={minTip}
onChange={(e) => {
const newMinTip = parseFloat(e.target.value);
setMinTip(newMinTip);
pushMinTipDebounced(newMinTip, activeChannelClaim);
if (newMinTip !== 0 && minSuper !== 0) {
setMinSuper(0);
pushMinSuperDebounced(0, activeChannelClaim);
}
}}
onBlur={() => setLastUpdated(Date.now())}
/>
</SettingsRow>
<SettingsRow
title={
<I18nMessage tokens={{ lbc: <LbcSymbol /> }}>Minimum %lbc% tip amount for hyperchats</I18nMessage>
}
subtitle={
<>
{__(HELP.MIN_SUPER)}
{minTip !== 0 && (
<p className="help--inline">
<em>{__(HELP.MIN_SUPER_OFF)}</em>
</p>
)}
</>
}
>
<FormField
name="min_tip_amount_super_chat"
className="form-field--price-amount"
min={0}
step="any"
type="number"
placeholder="1"
value={minSuper}
disabled={minTip !== 0}
onChange={(e) => {
const newMinSuper = parseFloat(e.target.value);
setMinSuper(newMinSuper);
pushMinSuperDebounced(newMinSuper, activeChannelClaim);
}}
onBlur={() => setLastUpdated(Date.now())}
/>
</SettingsRow>
<SettingsRow title={__('Filter')} subtitle={__(HELP.BLOCKED_WORDS)} multirow>
<div className="tag--blocked-words">
<TagsSearch
label={__('Muted words')}
labelAddNew={__('Add words')}
labelSuggestions={__('Suggestions')}
onRemove={removeMutedWord}
onSelect={addMutedWords}
disableAutoFocus
tagsPassedIn={mutedWordTags}
placeholder={__('Add words to block')}
hideSuggestions
disableControlTags
/>
</div>
</SettingsRow>
<SettingsRow title={__('Moderators')} subtitle={__(HELP.MODERATORS)} multirow>
<div className="tag--blocked-words">
<TagsSearch
label={__('Moderators')}
labelAddNew={__('Add moderator')}
onRemove={removeModerator}
onSelect={addModerator}
tagsPassedIn={moderatorTags}
disableAutoFocus
hideInputField
hideSuggestions
disableControlTags
/>
<FormField
type="text"
name="moderator_search"
className="form-field--address"
label={
<>
{__('Search channel')}
<Icon
customTooltipText={__(HELP.MODERATOR_SEARCH)}
className="icon--help"
icon={ICONS.HELP}
tooltip
size={16}
/>
</>
}
placeholder={__('Enter a @username or URL')}
value={moderatorSearchTerm}
onChange={(e) => setModeratorSearchTerm(e.target.value)}
error={moderatorSearchError}
/>
{moderatorSearchClaimUri && (
<div className="section">
<ClaimPreview
key={moderatorSearchClaimUri}
uri={moderatorSearchClaimUri}
// type={'small'}
// showNullPlaceholder
hideMenu
hideRepostLabel
disableNavigation
properties={''}
renderActions={(claim) => {
return (
<Button
requiresAuth
button="primary"
label={__('Add as moderator')}
onClick={() => handleChannelSearchSelect(claim)}
/>
);
}}
/>
</div>
)}
</div>
</SettingsRow>
</>
}
/>
</>
)}
</div>
</Page>
);
}
// prettier-ignore
const HELP = {
SLOW_MODE: 'Minimum time gap in seconds between comments (affects livestream chat as well).',
MIN_TIP: 'Enabling a minimum amount to comment will force all comments, including livestreams, to have tips associated with them. This can help prevent spam.',
MIN_SUPER: 'Enabling a minimum amount to hyperchat will force all TIPPED comments to have this value in order to be shown. This still allows regular comments to be posted.',
MIN_SUPER_OFF: '(This settings is not applicable if all comments require a tip.)',
BLOCKED_WORDS: 'Comments and livestream chat containing these words will be blocked.',
MODERATORS: 'Moderators can block channels on your behalf. Blocked channels will appear in your "Blocked and Muted" list.',
MODERATOR_SEARCH: 'Enter a channel name or URL to add as a moderator.\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8',
};

View file

@ -6,6 +6,7 @@ import * as React from 'react';
import Page from 'component/page';
import { FormField } from 'component/common/form';
import Card from 'component/common/card';
import SettingsRow from 'component/settingsRow';
import { Lbryio } from 'lbryinc';
import { useHistory } from 'react-router';
import { Redirect } from 'react-router-dom';
@ -33,12 +34,12 @@ export default function NotificationSettingsPage(props: Props) {
React.useEffect(() => {
Lbryio.call('tag', 'list', lbryIoParams)
.then(setTags)
.catch(e => {
.catch((e) => {
setError(true);
});
Lbryio.call('user_email', 'status', lbryIoParams)
.then(res => {
.then((res) => {
const enabledEmails =
res.emails &&
Object.keys(res.emails).reduce((acc, email) => {
@ -49,7 +50,7 @@ export default function NotificationSettingsPage(props: Props) {
setTagMap(res.tags);
setEnabledEmails(enabledEmails);
})
.catch(e => {
.catch((e) => {
setError(true);
});
}, []);
@ -73,7 +74,7 @@ export default function NotificationSettingsPage(props: Props) {
})
.then(() => {
const newEnabledEmails = enabledEmails
? enabledEmails.map(userEmail => {
? enabledEmails.map((userEmail) => {
if (email === userEmail.email) {
return { email, isEnabled: newIsEnabled };
}
@ -84,7 +85,7 @@ export default function NotificationSettingsPage(props: Props) {
setEnabledEmails(newEnabledEmails);
})
.catch(e => {
.catch((e) => {
setError(true);
});
}
@ -94,7 +95,13 @@ export default function NotificationSettingsPage(props: Props) {
}
return (
<Page backout={{ title: __('Manage notifications'), backLabel: __('Done') }} noFooter noSideNavigation>
<Page
noFooter
noSideNavigation
settingsPage
className="card-stack"
backout={{ title: __('Manage notifications'), backLabel: __('Back') }}
>
{error ? (
<Yrbl
type="sad"
@ -115,66 +122,89 @@ export default function NotificationSettingsPage(props: Props) {
) : (
<div className="card-stack">
{/* @if TARGET='app' */}
<div>
<h2 className="card__title">{__('App notifications')}</h2>
<div className="card__subtitle">{__('Notification settings for the desktop app.')}</div>
</div>
<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 an upload or channel is confirmed.')}
/>
isBodyList
body={
<SettingsRow
title={__('Show Desktop Notifications')}
subtitle={__('Get notified when an upload or channel is confirmed.')}
>
<FormField
type="checkbox"
name="desktopNotification"
onChange={() => setClientSetting(SETTINGS.OS_NOTIFICATIONS_ENABLED, !osNotificationsEnabled)}
checked={osNotificationsEnabled}
/>
</SettingsRow>
}
/>
{/* @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}`}
key={email}
onChange={() => handleChangeEmail(email, !isEnabled)}
checked={isEnabled}
label={email}
/>
))}
</>
}
/>
<>
<div>
<h2 className="card__title">
{enabledEmails.length === 1 ? __('Your email') : __('Receiving addresses')}
</h2>
<div className="card__subtitle">
{__('Uncheck your email below if you want to stop receiving messages.')}
</div>
</div>
<Card
isBodyList
body={
<>
{enabledEmails.map(({ email, isEnabled }) => (
<SettingsRow key={email} subtitle={__(email)}>
<FormField
type="checkbox"
name={`active-email:${email}`}
key={email}
onChange={() => handleChangeEmail(email, !isEnabled)}
checked={isEnabled}
/>
</SettingsRow>
))}
</>
}
/>
</>
)}
{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>
<h2 className="card__title">{__('Email preferences')}</h2>
<div className="card__subtitle">
{__("Opt out of any topics you don't want to receive email about.")}
</div>
</div>
<Card
isBodyList
body={
<>
{tags.map((tag) => {
const isEnabled = tagMap[tag.name];
return (
<SettingsRow key={tag.name} subtitle={__(tag.description)}>
<FormField
type="checkbox"
key={tag.name}
name={tag.name}
onChange={() => handleChangeTag(tag.name, !isEnabled)}
checked={isEnabled}
/>
</SettingsRow>
);
})}
</>
}
/>
</>
)}
</div>
)}

View file

@ -194,7 +194,13 @@ class StripeAccountConnection extends React.Component<Props, State> {
} = this.state;
return (
<Page backout={{ title: pageTitle, backLabel: __('Done') }} noFooter noSideNavigation>
<Page
noFooter
noSideNavigation
settingsPage
className="card-stack"
backout={{ title: pageTitle, backLabel: __('Back') }}
>
<Card
title={<div className="table__header-text">{__('Connect a bank account')}</div>}
isBodyList

View file

@ -354,7 +354,13 @@ class SettingsStripeCard extends React.Component<Props, State> {
const { currentFlowStage, pageTitle, userCardDetails, paymentMethodId } = this.state;
return (
<Page backout={{ title: pageTitle, backLabel: __('Done') }} noFooter noSideNavigation>
<Page
noFooter
noSideNavigation
settingsPage
className="card-stack"
backout={{ title: pageTitle, backLabel: __('Back') }}
>
<div>
{scriptFailedToLoad && (
<div className="error__text">{__('There was an error connecting to Stripe. Please try again later.')}</div>

View file

@ -26,12 +26,6 @@
--color-help-warning-text: var(--color-white-alt);
--color-help-warning-bg: #fbbf2450;
// Tags (words)
--color-tag-words: var(--color-text);
--color-tag-words-bg: var(--color-gray-5);
--color-tag-words-hover: var(--color-white);
--color-tag-words-bg-hover: var(--color-gray-4);
// Header
--color-header-button: #38274c;
--color-header-background: #231830;

View file

@ -115,10 +115,10 @@
--color-tag-bg-hover: var(--color-button-primary-bg);
// Tags (words)
--color-tag-words: var(--color-gray-5);
--color-tag-words-bg: var(--color-button-alt-bg);
--color-tag-words-hover: var(--color-button-alt-text);
--color-tag-words-bg-hover: var(--color-button-alt-bg-hover);
--color-tag-words: var(--color-primary);
--color-tag-words-bg: var(--color-primary-alt);
--color-tag-words-hover: var(--color-primary);
--color-tag-words-bg-hover: var(--color-primary-alt-3);
// Menu
--color-menu-background: var(--color-header-background);