diff --git a/static/app-strings.json b/static/app-strings.json index 7b2e6774a..f786b48e7 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -156,8 +156,9 @@ "Experimental settings": "Experimental settings", "Autoplay media files": "Autoplay media files", "Autoplay video and audio files when navigating to a file, as well as the next related item when a file finishes playing.": "Autoplay video and audio files when navigating to a file, as well as the next related item when a file finishes playing.", - "Application cache": "Application cache", + "Clear application cache": "Clear application cache", "Clear Cache": "Clear Cache", + "This might fix issues that you are having. Your wallet will not be affected.": "This might fix issues that you are having. Your wallet will not be affected.", "Currency": "Currency", "US Dollars": "US Dollars", "There's nothing available at this location.": "There's nothing available at this location.", @@ -493,7 +494,7 @@ "Automatic dark mode": "Automatic dark mode", "24-hour clock": "24-hour clock", "Hide wallet balance in header": "Hide wallet balance in header", - "Max Connections": "Max Connections", + "Max connections": "Max connections", "For users with good bandwidth, try a higher value to improve streaming and download speeds. Low bandwidth users may benefit from a lower setting. Default is 4.": "For users with good bandwidth, try a higher value to improve streaming and download speeds. Low bandwidth users may benefit from a lower setting. Default is 4.", "Show All": "Show All", "LBRY names cannot contain spaces or reserved symbols": "LBRY names cannot contain spaces or reserved symbols", @@ -511,7 +512,7 @@ "Read more": "Read more", "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences, like glossolalia.": "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences, like glossolalia.", "Tailor your experience.": "Tailor your experience.", - "Save Password": "Save Password", + "Save wallet password": "Save wallet password", "Automatically unlock your wallet on startup": "Automatically unlock your wallet on startup", "Dark": "Dark", "light": "light", @@ -656,7 +657,6 @@ "Invalid claim ID %claimId%.": "Invalid claim ID %claimId%.", "Suggested": "Suggested", "Startup preferences": "Startup preferences", - "This will clear the application cache, and might fix issues you are having. Your wallet will not be affected. ": "This will clear the application cache, and might fix issues you are having. Your wallet will not be affected. ", "Start minimized": "Start minimized", "Improve view speed and help the LBRY network by allowing the app to cuddle up in your system tray.": "Improve view speed and help the LBRY network by allowing the app to cuddle up in your system tray.", "Content Type": "Content Type", @@ -710,7 +710,8 @@ "Failed to copy RSS URL.": "Failed to copy RSS URL.", "Text copied": "Text copied", "Rewards Disabled": "Rewards Disabled", - "Wallet servers are used to relay data to and from the LBRY blockchain. They also determine what content shows in trending or is blocked. %learn_more%.": "Wallet servers are used to relay data to and from the LBRY blockchain. They also determine what content shows in trending or is blocked. %learn_more%.", + "Wallet server": "Wallet server", + "Wallet servers are used to relay data to and from the LBRY blockchain. They also determine what content shows in trending or is blocked. %learn_more%": "Wallet servers are used to relay data to and from the LBRY blockchain. They also determine what content shows in trending or is blocked. %learn_more%", "Your Tags": "Your Tags", "All Content": "All Content", "Accepted": "Accepted", @@ -1419,7 +1420,9 @@ "Transcode": "Transcode", "Estimated transaction fee:": "Estimated transaction fee:", "Est. transaction fee:": "Est. transaction fee:", + "Publish confirmation": "Publish confirmation", "Skip preview and confirmation": "Skip preview and confirmation", + "Show preview and confirmation dialog before publishing content.": "Show preview and confirmation dialog before publishing content.", "Upload settings": "Upload settings", "Currently Uploading": "Currently Uploading", "Leave the app running until upload is complete": "Leave the app running until upload is complete", @@ -1610,7 +1613,7 @@ "None selected": "None selected", "Secondary Language": "Secondary Language", "Your other content language": "Your other content language", - "Search only in this language by default": "Search only in this language by default", + "Search only in the selected language by default": "Search only in the selected language by default", "This link leads to an external website.": "This link leads to an external website.", "No Content Found": "No Content Found", "No Lists Found": "No Lists Found", @@ -1743,8 +1746,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 (<, >)", @@ -1933,6 +1937,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", @@ -2047,6 +2052,7 @@ "Commenting server is not set.": "Commenting server is not set.", "Comments are not currently enabled.": "Comments are not currently enabled.", "See All": "See All", + "System": "System", "Supporting content requires %lbc%": "Supporting content requires %lbc%", "With %lbc%, you can send tips to your favorite creators, or help boost their content for more people to see.": "With %lbc%, you can send tips to your favorite creators, or help boost their content for more people to see.", "Show this channel your appreciation by sending a donation in USD.": "Show this channel your appreciation by sending a donation in USD.", diff --git a/ui/component/common/card.jsx b/ui/component/common/card.jsx index 29c3f1a9c..1fb606cdb 100644 --- a/ui/component/common/card.jsx +++ b/ui/component/common/card.jsx @@ -10,6 +10,7 @@ type Props = { title?: string | Node, subtitle?: string | Node, titleActions?: string | Node, + id?: string, body?: string | Node, actions?: string | Node, icon?: string, @@ -30,6 +31,7 @@ export default function Card(props: Props) { title, subtitle, titleActions, + id, body, actions, icon, @@ -53,6 +55,7 @@ export default function Card(props: Props) { className={classnames(className, 'card', { 'card__multi-pane': Boolean(secondPane), })} + id={id} onClick={(e) => { if (onClick) { onClick(); diff --git a/ui/component/common/icon-custom.jsx b/ui/component/common/icon-custom.jsx index c9a181801..fcd9e0954 100644 --- a/ui/component/common/icon-custom.jsx +++ b/ui/component/common/icon-custom.jsx @@ -2318,6 +2318,23 @@ export const icons = { ), + [ICONS.APPEARANCE]: buildIcon( + + + + + + + + ), + [ICONS.CONTENT]: buildIcon( + + + + + + + ), [ICONS.STAR]: buildIcon( diff --git a/ui/component/homepageSelector/view.jsx b/ui/component/homepageSelector/view.jsx index 514cb2f94..b8eb7adef 100644 --- a/ui/component/homepageSelector/view.jsx +++ b/ui/component/homepageSelector/view.jsx @@ -8,7 +8,7 @@ import { getDefaultHomepageKey } from 'util/default-languages'; type Props = { homepage: string, - setHomepage: string => void, + setHomepage: (string) => void, }; function SelectHomepage(props: Props) { @@ -26,12 +26,10 @@ function SelectHomepage(props: Props) { - {Object.keys(homepages).map(hp => ( + {Object.keys(homepages).map((hp) => ( diff --git a/ui/component/maxPurchasePrice/index.js b/ui/component/maxPurchasePrice/index.js new file mode 100644 index 000000000..ed04f6cfe --- /dev/null +++ b/ui/component/maxPurchasePrice/index.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; +import { doSetDaemonSetting } from 'redux/actions/settings'; +import { selectDaemonSettings } from 'redux/selectors/settings'; +import MaxPurchasePrice from './view'; + +const select = (state) => ({ + daemonSettings: selectDaemonSettings(state), +}); +const perform = (dispatch) => ({ + setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)), +}); + +export default connect(select, perform)(MaxPurchasePrice); diff --git a/ui/component/maxPurchasePrice/view.jsx b/ui/component/maxPurchasePrice/view.jsx new file mode 100644 index 000000000..bd163ec81 --- /dev/null +++ b/ui/component/maxPurchasePrice/view.jsx @@ -0,0 +1,72 @@ +// @flow +import React from 'react'; +import { FormField, FormFieldPrice } from 'component/common/form'; + +type Price = { + currency: string, + amount: number, +}; + +type DaemonSettings = { + download_dir: string, + share_usage_data: boolean, + max_key_fee?: Price, + max_connections_per_download?: number, + save_files: boolean, + save_blobs: boolean, + ffmpeg_path: string, +}; + +type SetDaemonSettingArg = boolean | string | number | Price; + +type Props = { + daemonSettings: DaemonSettings, + setDaemonSetting: (string, ?SetDaemonSettingArg) => void, +}; + +export default function MaxPurchasePrice(props: Props) { + const { daemonSettings, setDaemonSetting } = props; + + const defaultMaxKeyFee = { currency: 'USD', amount: 50 }; + const disableMaxKeyFee = !(daemonSettings && daemonSettings.max_key_fee); + + function onKeyFeeDisableChange(isDisabled: boolean) { + if (isDisabled) { + setDaemonSetting('max_key_fee'); + } + } + + function onKeyFeeChange(newValue: Price) { + setDaemonSetting('max_key_fee', newValue); + } + + return ( + <> + onKeyFeeDisableChange(true)} + /> + { + onKeyFeeDisableChange(false); + onKeyFeeChange(defaultMaxKeyFee); + }} + label={__('Choose limit')} + /> + + + + ); +} diff --git a/ui/component/page/view.jsx b/ui/component/page/view.jsx index 3951ad603..6d288c3bc 100644 --- a/ui/component/page/view.jsx +++ b/ui/component/page/view.jsx @@ -4,6 +4,7 @@ import React, { Fragment } from 'react'; import classnames from 'classnames'; import { lazyImport } from 'util/lazyImport'; import SideNavigation from 'component/sideNavigation'; +import SettingsSideNavigation from 'component/settingsSideNavigation'; import Header from 'component/header'; /* @if TARGET='app' */ import StatusBar from 'component/common/status-bar'; @@ -23,6 +24,7 @@ type Props = { isUpgradeAvailable: boolean, authPage: boolean, filePage: boolean, + settingsPage?: boolean, noHeader: boolean, noFooter: boolean, noSideNavigation: boolean, @@ -45,6 +47,7 @@ function Page(props: Props) { children, className, filePage = false, + settingsPage, authPage = false, fullWidthPage = false, noHeader = false, @@ -76,6 +79,24 @@ function Page(props: Props) { const isAbsoluteSideNavHidden = (isOnFilePage || isMobile) && !sidebarOpen; + function getSideNavElem() { + if (!authPage) { + if (settingsPage) { + return ; + } else if (!noSideNavigation) { + return ( + + ); + } + } + return null; + } + React.useEffect(() => { if (isOnFilePage || isMediumScreen) { setSidebarOpen(false); @@ -100,20 +121,15 @@ function Page(props: Props) { 'main-wrapper__inner--theater-mode': isOnFilePage && videoTheaterMode, })} > - {!authPage && !noSideNavigation && ( - - )} + {getSideNavElem()} +
({ - enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state), -}); - -const perform = dispatch => ({ - setEnablePublishPreview: value => dispatch(doSetClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW, value)), -}); - -export default connect(select, perform)(PublishSettings); diff --git a/ui/component/publishSettings/view.jsx b/ui/component/publishSettings/view.jsx deleted file mode 100644 index d59d4e6ea..000000000 --- a/ui/component/publishSettings/view.jsx +++ /dev/null @@ -1,31 +0,0 @@ -// @flow -import React from 'react'; -import { FormField } from 'component/common/form'; -import { withRouter } from 'react-router'; - -type Props = { - enablePublishPreview: boolean, - setEnablePublishPreview: boolean => void, -}; - -function PublishSettings(props: Props) { - const { enablePublishPreview, setEnablePublishPreview } = props; - - function handleChange() { - setEnablePublishPreview(!enablePublishPreview); - } - - return ( -
- -
- ); -} - -export default withRouter(PublishSettings); diff --git a/ui/component/router/view.jsx b/ui/component/router/view.jsx index 1e2631970..eb6f74fcf 100644 --- a/ui/component/router/view.jsx +++ b/ui/component/router/view.jsx @@ -67,9 +67,10 @@ const RepostNew = lazyImport(() => import('page/repost' /* webpackChunkName: "se const RewardsPage = lazyImport(() => import('page/rewards' /* webpackChunkName: "secondary" */)); const RewardsVerifyPage = lazyImport(() => import('page/rewardsVerify' /* webpackChunkName: "secondary" */)); const SearchPage = lazyImport(() => import('page/search' /* webpackChunkName: "secondary" */)); -const SettingsAdvancedPage = lazyImport(() => import('page/settingsAdvanced' /* webpackChunkName: "secondary" */)); const SettingsStripeCard = lazyImport(() => import('page/settingsStripeCard' /* webpackChunkName: "secondary" */)); -const SettingsStripeAccount = lazyImport(() => import('page/settingsStripeAccount' /* webpackChunkName: "secondary" */)); +const SettingsStripeAccount = lazyImport(() => + import('page/settingsStripeAccount' /* webpackChunkName: "secondary" */) +); const SettingsCreatorPage = lazyImport(() => import('page/settingsCreator' /* webpackChunkName: "secondary" */)); const SettingsNotificationsPage = lazyImport(() => import('page/settingsNotifications' /* webpackChunkName: "secondary" */) @@ -81,6 +82,7 @@ const TagsFollowingManagePage = lazyImport(() => ); const TagsFollowingPage = lazyImport(() => import('page/tagsFollowing' /* webpackChunkName: "secondary" */)); const TopPage = lazyImport(() => import('page/top' /* webpackChunkName: "secondary" */)); +const UpdatePasswordPage = lazyImport(() => import('page/passwordUpdate' /* webpackChunkName: "passwordUpdate" */)); const Welcome = lazyImport(() => import('page/welcome' /* webpackChunkName: "secondary" */)); const YoutubeSyncPage = lazyImport(() => import('page/youtubeSync' /* webpackChunkName: "secondary" */)); @@ -277,7 +279,6 @@ function AppRouter(props: Props) { - @@ -294,6 +295,7 @@ function AppRouter(props: Props) { + ({ + isAuthenticated: selectUserVerifiedEmail(state), + walletEncrypted: selectWalletIsEncrypted(state), + user: selectUser(state), + language: selectLanguage(state), +}); + +const perform = (dispatch) => ({ + doWalletStatus: () => dispatch(doWalletStatus()), +}); + +export default connect(select, perform)(SettingAccount); diff --git a/ui/component/settingAccount/view.jsx b/ui/component/settingAccount/view.jsx new file mode 100644 index 000000000..bca8861b1 --- /dev/null +++ b/ui/component/settingAccount/view.jsx @@ -0,0 +1,100 @@ +// @flow +import * as ICONS from 'constants/icons'; +import * as PAGES from 'constants/pages'; +import { SETTINGS_GRP } from 'constants/settings'; +import React from 'react'; +import Button from 'component/button'; +import Card from 'component/common/card'; +import SettingsRow from 'component/settingsRow'; +import SyncToggle from 'component/syncToggle'; +import { getPasswordFromCookie } from 'util/saved-passwords'; +import { getStripeEnvironment } from 'util/stripe'; + +type Props = { + // --- select --- + isAuthenticated: boolean, + walletEncrypted: boolean, + user: User, + // --- perform --- + doWalletStatus: () => void, +}; + +export default function SettingAccount(props: Props) { + const { isAuthenticated, walletEncrypted, user, doWalletStatus } = props; + const [storedPassword, setStoredPassword] = React.useState(false); + + // Determine if password is stored. + React.useEffect(() => { + if (isAuthenticated || !IS_WEB) { + doWalletStatus(); + getPasswordFromCookie().then((p) => { + if (typeof p === 'string') { + setStoredPassword(true); + } + }); + } + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + return ( + <> +
+

{__('Account')}

+
+ + + {isAuthenticated && ( + +