diff --git a/.env.defaults b/.env.defaults index afdf15884..b1da81ebe 100644 --- a/.env.defaults +++ b/.env.defaults @@ -26,16 +26,15 @@ MATOMO_URL=https://analytics.lbry.com/ MATOMO_ID=4 # OG -OG_TITLE_SUFFIX=| lbry.com -OG_HOMEPAGE_TITLE=lbry.com +OG_TITLE_SUFFIX=| lbry.tv +OG_HOMEPAGE_TITLE=lbry.tv OG_IMAGE_URL= -SITE_CANONICAL_URL=https://lbry.com +SITE_CANONICAL_URL=https://lbry.tv # UI ## Custom Site info -DOMAIN=lbry.com -CLOUD_DOMAIN=odysee.com -URL=https://lbry.com +DOMAIN=lbry.tv +URL=https://lbry.tv SITE_TITLE=LBRY SITE_NAME=LBRY SITE_DESCRIPTION=Meet LBRY, an open, free, and community-controlled content wonderland. diff --git a/config.js b/config.js index e976dd99a..c98f4dac1 100644 --- a/config.js +++ b/config.js @@ -21,7 +21,6 @@ const config = { WELCOME_VERSION: process.env.WELCOME_VERSION, DOMAIN: process.env.DOMAIN, SHARE_DOMAIN_URL: process.env.SHARE_DOMAIN_URL, - CLOUD_DOMAIN: process.env.CLOUD_DOMAIN, URL: process.env.URL, THUMBNAIL_CDN_URL: process.env.THUMBNAIL_CDN_URL, SITE_TITLE: process.env.SITE_TITLE, diff --git a/extras/lbryinc/lbryio.js b/extras/lbryinc/lbryio.js index 3359f5b72..89c0536e9 100644 --- a/extras/lbryinc/lbryio.js +++ b/extras/lbryinc/lbryio.js @@ -155,7 +155,10 @@ Lbryio.authenticate = (domain, language) => { .then( status => new Promise((res, rej) => { - const appId = status.installation_id; + const appId = + domain && domain !== 'lbry.tv' + ? (domain.replace(/[.]/gi, '') + status.installation_id).slice(0, 66) + : status.installation_id; Lbryio.call( 'user', 'new', diff --git a/ui/page/youtubeSync/index.js b/ui/page/youtubeSync/index.js new file mode 100644 index 000000000..6163f9fcc --- /dev/null +++ b/ui/page/youtubeSync/index.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux'; +import { selectYoutubeChannels } from 'redux/selectors/user'; +import { doUserFetch } from 'redux/actions/user'; +import CreatorDashboardPage from './view'; + +const select = state => ({ + youtubeChannels: selectYoutubeChannels(state), +}); + +export default connect(select, { + doUserFetch, +})(CreatorDashboardPage); diff --git a/ui/page/youtubeSync/view.jsx b/ui/page/youtubeSync/view.jsx new file mode 100644 index 000000000..69e26a614 --- /dev/null +++ b/ui/page/youtubeSync/view.jsx @@ -0,0 +1,216 @@ +// @flow +import { SITE_NAME, DOMAIN } from 'config'; +import * as PAGES from 'constants/pages'; +import SUPPORTED_LANGUAGES from 'constants/supported_languages'; +import React from 'react'; +import Page from 'component/page'; +import Button from 'component/button'; +import Card from 'component/common/card'; +import I18nMessage from 'component/i18nMessage'; +import { Form, FormField } from 'component/common/form'; +import { INVALID_NAME_ERROR } from 'constants/claim'; +import { isNameValid } from 'util/lbryURI'; +import { Lbryio } from 'lbryinc'; +import { useHistory } from 'react-router'; +import YoutubeTransferStatus from 'component/youtubeTransferStatus'; +import Nag from 'component/common/nag'; +import { getDefaultLanguage, sortLanguageMap } from 'util/default-languages'; + +const STATUS_TOKEN_PARAM = 'status_token'; +const ERROR_MESSAGE_PARAM = 'error_message'; +const NEW_CHANNEL_PARAM = 'new_channel'; + +type Props = { + youtubeChannels: ?Array<{ transfer_state: string, sync_status: string }>, + doUserFetch: () => void, + inSignUpFlow?: boolean, + doToggleInterestedInYoutubeSync: () => void, +}; + +export default function YoutubeSync(props: Props) { + const { youtubeChannels, doUserFetch, inSignUpFlow = false, doToggleInterestedInYoutubeSync } = props; + const { + location: { search, pathname }, + push, + replace, + } = useHistory(); + const urlParams = new URLSearchParams(search); + const statusToken = urlParams.get(STATUS_TOKEN_PARAM); + const errorMessage = urlParams.get(ERROR_MESSAGE_PARAM); + const newChannelParam = urlParams.get(NEW_CHANNEL_PARAM); + const [channel, setChannel] = React.useState(''); + const [language, setLanguage] = React.useState(getDefaultLanguage()); + const [nameError, setNameError] = React.useState(undefined); + const [acknowledgedTerms, setAcknowledgedTerms] = React.useState(false); + const [addingNewChannel, setAddingNewChannel] = React.useState(newChannelParam); + const hasYoutubeChannels = youtubeChannels && youtubeChannels.length > 0; + + React.useEffect(() => { + const urlParamsInEffect = new URLSearchParams(search); + if (!urlParamsInEffect.get('reset_scroll')) { + urlParamsInEffect.append('reset_scroll', 'youtube'); + } + + replace(`?${urlParamsInEffect.toString()}`); + }, [pathname, search]); + + React.useEffect(() => { + if (statusToken && !hasYoutubeChannels) { + doUserFetch(); + } + }, [statusToken, hasYoutubeChannels, doUserFetch]); + + React.useEffect(() => { + if (!newChannelParam) { + setAddingNewChannel(false); + } + }, [newChannelParam]); + + function handleCreateChannel() { + Lbryio.call('yt', 'new', { + type: 'sync', + immediate_sync: true, + channel_language: language, + desired_lbry_channel_name: `@${channel}`, + return_url: `https://${DOMAIN}/$/${inSignUpFlow ? PAGES.AUTH : PAGES.YOUTUBE_SYNC}`, + }).then((ytAuthUrl) => { + // react-router isn't needed since it's a different domain + window.location.href = ytAuthUrl; + }); + } + + function handleChannelChange(e) { + const { value } = e.target; + setChannel(value); + if (!isNameValid(value)) { + setNameError(INVALID_NAME_ERROR); + } else { + setNameError(); + } + } + + function handleNewChannel() { + urlParams.append('new_channel', 'true'); + push(`${pathname}?${urlParams.toString()}`); + setAddingNewChannel(true); + } + + const Wrapper = (props: { children: any }) => { + return inSignUpFlow ? ( + <>{props.children} + ) : ( + + {props.children} + + ); + }; + + return ( + +
+ {hasYoutubeChannels && !addingNewChannel ? ( + + ) : ( + + + + +
@
+
+ + +
+ setLanguage(event.target.value)} + value={language} + > + {sortLanguageMap(SUPPORTED_LANGUAGES).map(([langKey, langName]) => ( + + ))} + + setAcknowledgedTerms(!acknowledgedTerms)} + label={ + + ), + faq: ( +
+
+ , + }} + > + This will verify you are an active YouTuber. Channel names cannot be changed once chosen, please be + extra careful. Additional instructions will be emailed to you after you verify your email on the + next page. %learn_more%. + +
+ + } + nag={errorMessage && } + /> + )} + +
+ ); +} diff --git a/ui/redux/actions/app.js b/ui/redux/actions/app.js index f3df75f3d..2665ae06b 100644 --- a/ui/redux/actions/app.js +++ b/ui/redux/actions/app.js @@ -7,7 +7,7 @@ import * as MODALS from 'constants/modal_types'; import * as SETTINGS from 'constants/settings'; import * as DAEMON_SETTINGS from 'constants/daemon_settings'; import * as SHARED_PREFERENCES from 'constants/shared_preferences'; -import { CLOUD_DOMAIN } from 'config'; +import { DOMAIN } from 'config'; import Lbry from 'lbry'; import { doFetchChannelListMine, doFetchCollectionListMine, doCheckPendingClaims } from 'redux/actions/claims'; import { selectClaimForUri, selectClaimIsMineForUri, selectMyChannelClaims } from 'redux/selectors/claims'; @@ -336,7 +336,7 @@ export function doDaemonReady() { } }, undefined, - CLOUD_DOMAIN + DOMAIN ) ); dispatch({ type: ACTIONS.DAEMON_READY }); diff --git a/ui/redux/actions/user.js b/ui/redux/actions/user.js index 011f41ed1..10cbe4103 100644 --- a/ui/redux/actions/user.js +++ b/ui/redux/actions/user.js @@ -9,11 +9,42 @@ import { doClaimRewardType, doRewardList } from 'redux/actions/rewards'; import { selectEmailToVerify, selectPhoneToVerify, selectUserCountryCode, selectUser } from 'redux/selectors/user'; import rewards from 'rewards'; import { Lbryio } from 'lbryinc'; -import { CLOUD_DOMAIN } from 'config'; +import { DOMAIN } from 'config'; import { getDefaultLanguage } from 'util/default-languages'; const AUTH_IN_PROGRESS = 'authInProgress'; export let sessionStorageAvailable = false; +export function doFetchInviteStatus(shouldCallRewardList = true) { + return (dispatch) => { + dispatch({ + type: ACTIONS.USER_INVITE_STATUS_FETCH_STARTED, + }); + + Promise.all([Lbryio.call('user', 'invite_status'), Lbryio.call('user_referral_code', 'list')]) + .then(([status, code]) => { + if (shouldCallRewardList) { + dispatch(doRewardList()); + } + + dispatch({ + type: ACTIONS.USER_INVITE_STATUS_FETCH_SUCCESS, + data: { + invitesRemaining: status.invites_remaining ? status.invites_remaining : 0, + invitees: status.invitees, + referralLink: `${Lbryio.CONNECTION_STRING}user/refer?r=${code}`, + referralCode: code, + }, + }); + }) + .catch((error) => { + dispatch({ + type: ACTIONS.USER_INVITE_STATUS_FETCH_FAILURE, + data: { error }, + }); + }); + }; +} + export function doInstallNew(appVersion, os = null, firebaseToken = null, callbackForUsersWhoAreSharingData, domain) { const payload = { app_version: appVersion, domain }; if (firebaseToken) { @@ -39,6 +70,30 @@ export function doInstallNew(appVersion, os = null, firebaseToken = null, callba }); } +export function doInstallNewWithParams( + appVersion, + installationId, + nodeId, + lbrynetVersion, + os, + platform, + firebaseToken = null +) { + return () => { + const payload = { app_version: appVersion }; + if (firebaseToken) { + payload.firebase_token = firebaseToken; + } + + payload.app_id = installationId; + payload.node_id = nodeId; + payload.daemon_version = lbrynetVersion; + payload.operating_system = os; + payload.platform = platform; + Lbryio.call('install', 'new', payload); + }; +} + // TODO: Call doInstallNew separately so we don't have to pass appVersion and os_system params? export function doAuthenticate( appVersion, @@ -53,7 +108,7 @@ export function doAuthenticate( dispatch({ type: ACTIONS.AUTHENTICATION_STARTED, }); - return Lbryio.authenticate(CLOUD_DOMAIN, getDefaultLanguage()) // lbry.tv + return Lbryio.authenticate(DOMAIN, getDefaultLanguage()) .then((user) => { if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS); Lbryio.getAuthToken().then((token) => {