remove redux user and rewards selectors, reducers, actions

This commit is contained in:
zeppi 2022-08-07 16:37:51 -04:00
parent d7b9ca3391
commit 7ab5a0c978
154 changed files with 201 additions and 6411 deletions

View file

@ -1,91 +0,0 @@
const path = require('path');
const fs = require('fs');
const packageJSON = require('../package.json');
const fetch = require('node-fetch');
const decompress = require('decompress');
const os = require('os');
const del = require('del');
const downloadLBRYFirst = targetPlatform =>
new Promise((resolve, reject) => {
const lbryFirstURLTemplate = packageJSON.lbrySettings.LBRYFirstUrlTemplate;
const lbryFirstVersion = packageJSON.lbrySettings.LBRYFirstVersion;
const lbryFirstDir = path.join(__dirname, '..', packageJSON.lbrySettings.LBRYFirstDir);
let lbryFirstFileName = packageJSON.lbrySettings.LBRYFirstFileName;
const currentPlatform = os.platform();
let lbryFirstPlatform = process.env.TARGET || targetPlatform || currentPlatform;
if (lbryFirstPlatform === 'linux') lbryFirstPlatform = 'Linux';
if (lbryFirstPlatform === 'mac' || lbryFirstPlatform === 'darwin') lbryFirstPlatform = 'Darwin';
if (lbryFirstPlatform === 'win32' || lbryFirstPlatform === 'windows') {
lbryFirstPlatform = 'Windows';
lbryFirstFileName += '.exe';
}
const lbryFirstFilePath = path.join(lbryFirstDir, lbryFirstFileName);
const lbryFirstVersionPath = path.join(__dirname, 'lbryFirst.ver');
const tmpZipPath = path.join(__dirname, '..', 'dist', 'lbryFirst.zip');
const lbryFirstURL = lbryFirstURLTemplate.replace(/LBRYFIRSTVER/g, lbryFirstVersion).replace(/OSNAME/g, lbryFirstPlatform);
console.log('URL:', lbryFirstURL);
// If a lbryFirst and lbryFirst.ver exists, check to see if it matches the current lbryFirst version
const hasLbryFirstDownloaded = fs.existsSync(lbryFirstFilePath);
const hasLbryFirstVersion = fs.existsSync(lbryFirstVersionPath);
let downloadedLbryFirstVersion;
if (hasLbryFirstVersion) {
downloadedLbryFirstVersion = fs.readFileSync(lbryFirstVersionPath, 'utf8');
}
if (hasLbryFirstDownloaded && hasLbryFirstVersion && downloadedLbryFirstVersion === lbryFirstVersion) {
console.log('\x1b[34minfo\x1b[0m LbryFirst already downloaded');
resolve('Done');
} else {
console.log('\x1b[34minfo\x1b[0m Downloading lbryFirst...');
fetch(lbryFirstURL, {
method: 'GET',
headers: {
'Content-Type': 'application/zip',
},
})
.then(response => response.buffer())
.then(
result =>
new Promise((newResolve, newReject) => {
const distPath = path.join(__dirname, '..', 'dist');
const hasDistFolder = fs.existsSync(distPath);
if (!hasDistFolder) {
fs.mkdirSync(distPath);
}
fs.writeFile(tmpZipPath, result, error => {
if (error) return newReject(error);
return newResolve();
});
})
)
.then(() => del(`${lbryFirstFilePath}*`))
.then()
.then(() =>
decompress(tmpZipPath, lbryFirstDir, {
filter: file => path.basename(file.path) === lbryFirstFileName,
})
)
.then(() => {
console.log('\x1b[32msuccess\x1b[0m LbryFirst downloaded!');
if (hasLbryFirstVersion) {
del(lbryFirstVersionPath);
}
fs.writeFileSync(lbryFirstVersionPath, lbryFirstVersion, 'utf8');
resolve('Done');
})
.catch(error => {
console.error(`\x1b[31merror\x1b[0m LbryFirst download failed due to: \x1b[35m${error}\x1b[0m`);
reject(error);
});
}
});
downloadLBRYFirst();

View file

@ -1,59 +0,0 @@
import path from 'path';
import { spawn, execSync } from 'child_process';
export default class LbryFirstInstance {
static lbryFirstPath =
process.env.LBRY_FIRST_DAEMON ||
(process.env.NODE_ENV === 'production'
? path.join(process.resourcesPath, 'static/lbry-first', 'lbry-first')
: path.join(__static, 'lbry-first/lbry-first'));
static headersPath =
process.env.LBRY_FIRST_DAEMON ||
(process.env.NODE_ENV === 'production'
? path.join(process.resourcesPath, 'static/lbry-first', 'headers')
: path.join(__static, 'lbry-first/headers'));
subprocess;
handlers;
constructor() {
this.handlers = [];
}
launch() {
let flags = ['serve'];
console.log(`LbryFirst: ${LbryFirstInstance.lbryFirstPath}`);
this.subprocess = spawn(LbryFirstInstance.lbryFirstPath, flags);
this.subprocess.stdout.on('data', data => console.log(`LbryFirst: ${data}`));
this.subprocess.stderr.on('data', data => console.error(`LbryFirst: ${data}`));
this.subprocess.on('exit', () => this.fire('exit'));
this.subprocess.on('error', error => console.error(`LbryFirst error: ${error}`));
}
quit() {
if (process.platform === 'win32') {
try {
execSync(`taskkill /pid ${this.subprocess.pid} /t /f`);
} catch (error) {
console.error(error.message);
}
} else {
this.subprocess.kill();
}
}
// Follows the publish/subscribe pattern
// Subscribe method
on(event, handler, context = handler) {
this.handlers.push({ event, handler: handler.bind(context) });
}
// Publish method
fire(event, args) {
this.handlers.forEach(topic => {
if (topic.event === event) topic.handler(args);
});
}
}

View file

@ -7,7 +7,6 @@ import https from 'https';
import { app, dialog, ipcMain, session, shell, BrowserWindow } from 'electron';
import { autoUpdater } from 'electron-updater';
import Lbry from 'lbry';
import LbryFirstInstance from './LbryFirstInstance';
import Daemon from './Daemon';
import isDev from 'electron-is-dev';
import createTray from './createTray';
@ -58,7 +57,6 @@ let rendererWindow;
let tray; // eslint-disable-line
let daemon;
let lbryFirst;
const appState = {};
const PROTOCOL = 'lbry';
@ -115,51 +113,6 @@ const startDaemon = async () => {
}
};
let isLbryFirstRunning = false;
const startLbryFirst = async () => {
if (isLbryFirstRunning) {
console.log('LbryFirst already running');
handleLbryFirstLaunched();
return;
}
console.log('LbryFirst: Starting...');
try {
lbryFirst = new LbryFirstInstance();
lbryFirst.on('exit', e => {
if (!isDev) {
lbryFirst = null;
isLbryFirstRunning = false;
if (!appState.isQuitting) {
dialog.showErrorBox(
'LbryFirst has Exited',
'The lbryFirst may have encountered an unexpected error, or another lbryFirst instance is already running. \n\n',
e
);
}
app.quit();
}
});
} catch (e) {
console.log('LbryFirst: Failed to create new instance\n\n', e);
}
console.log('LbryFirst: Running...');
try {
await lbryFirst.launch();
handleLbryFirstLaunched();
} catch (e) {
isLbryFirstRunning = false;
console.log('LbryFirst: Failed to start\n', e);
}
};
const handleLbryFirstLaunched = () => {
isLbryFirstRunning = true;
rendererWindow.webContents.send('lbry-first-launched');
};
// When we are starting the app, ensure there are no other apps already running
const gotSingleInstanceLock = app.requestSingleInstanceLock();
@ -271,10 +224,6 @@ app.on('will-quit', event => {
daemon.quit();
event.preventDefault();
}
if (lbryFirst) {
lbryFirst.quit();
event.preventDefault();
}
if (rendererWindow) {
tray.destroy();
@ -453,15 +402,6 @@ ipcMain.on('version-info-requested', () => {
requestLatestRelease();
});
ipcMain.on('launch-lbry-first', async () => {
try {
await startLbryFirst();
} catch (e) {
console.log('Failed to start LbryFirst');
console.log(e);
}
});
process.on('uncaughtException', error => {
console.log(error);
dialog.showErrorBox('Error Encountered', `Caught error: ${error}`);

View file

@ -1,184 +0,0 @@
// @flow
/*
LBRY FIRST does not work due to api changes
*/
import 'proxy-polyfill';
const CHECK_LBRYFIRST_STARTED_TRY_NUMBER = 200;
//
// Basic LBRYFIRST connection config
// Offers a proxy to call LBRYFIRST methods
//
const LbryFirst: LbryFirstTypes = {
isConnected: false,
connectPromise: null,
lbryFirstConnectionString: 'http://localhost:1337/rpc',
apiRequestHeaders: { 'Content-Type': 'application/json' },
// Allow overriding lbryFirst connection string (e.g. to `/api/proxy` for lbryweb)
setLbryFirstConnectionString: (value: string) => {
LbryFirst.lbryFirstConnectionString = value;
},
setApiHeader: (key: string, value: string) => {
LbryFirst.apiRequestHeaders = Object.assign(LbryFirst.apiRequestHeaders, { [key]: value });
},
unsetApiHeader: key => {
Object.keys(LbryFirst.apiRequestHeaders).includes(key) &&
delete LbryFirst.apiRequestHeaders['key'];
},
// Allow overriding Lbry methods
overrides: {},
setOverride: (methodName, newMethod) => {
LbryFirst.overrides[methodName] = newMethod;
},
getApiRequestHeaders: () => LbryFirst.apiRequestHeaders,
// LbryFirst Methods
status: (params = {}) => lbryFirstCallWithResult('status', params),
stop: () => lbryFirstCallWithResult('stop', {}),
version: () => lbryFirstCallWithResult('version', {}),
// Upload to youtube
upload: (params: { title: string, description: string, file_path: ?string } = {}) => {
// Only upload when originally publishing for now
if (!params.file_path) {
return Promise.resolve();
}
const uploadParams: {
Title: string,
Description: string,
FilePath: string,
Category: string,
Keywords: string,
} = {
Title: params.title,
Description: params.description,
FilePath: params.file_path,
Category: '',
Keywords: '',
};
return lbryFirstCallWithResult('youtube.Upload', uploadParams);
},
hasYTAuth: (token: string) => {
const hasYTAuthParams = {};
hasYTAuthParams.AuthToken = token;
return lbryFirstCallWithResult('youtube.HasAuth', hasYTAuthParams);
},
ytSignup: () => {
const emptyParams = {};
return lbryFirstCallWithResult('youtube.Signup', emptyParams);
},
remove: () => {
const emptyParams = {};
return lbryFirstCallWithResult('youtube.Remove', emptyParams);
},
// Connect to lbry-first
connect: () => {
if (LbryFirst.connectPromise === null) {
LbryFirst.connectPromise = new Promise((resolve, reject) => {
let tryNum = 0;
// Check every half second to see if the lbryFirst is accepting connections
function checkLbryFirstStarted() {
tryNum += 1;
LbryFirst.status()
.then(resolve)
.catch(() => {
if (tryNum <= CHECK_LBRYFIRST_STARTED_TRY_NUMBER) {
setTimeout(checkLbryFirstStarted, tryNum < 50 ? 400 : 1000);
} else {
reject(new Error('Unable to connect to LBRY'));
}
});
}
checkLbryFirstStarted();
});
}
// Flow thinks this could be empty, but it will always return a promise
// $FlowFixMe
return LbryFirst.connectPromise;
},
};
function checkAndParse(response) {
if (response.status >= 200 && response.status < 300) {
return response.json();
}
return response.json().then(json => {
let error;
if (json.error) {
const errorMessage = typeof json.error === 'object' ? json.error.message : json.error;
error = new Error(errorMessage);
} else {
error = new Error('Protocol error with unknown response signature');
}
return Promise.reject(error);
});
}
export function apiCall(method: string, params: ?{}, resolve: Function, reject: Function) {
const counter = new Date().getTime();
const paramsArray = [params];
const options = {
method: 'POST',
headers: LbryFirst.apiRequestHeaders,
body: JSON.stringify({
jsonrpc: '2.0',
method,
params: paramsArray,
id: counter,
}),
};
return fetch(LbryFirst.lbryFirstConnectionString, options)
.then(checkAndParse)
.then(response => {
const error = response.error || (response.result && response.result.error);
if (error) {
return reject(error);
}
return resolve(response.result);
})
.catch(reject);
}
function lbryFirstCallWithResult(name: string, params: ?{} = {}) {
return new Promise((resolve, reject) => {
apiCall(
name,
params,
result => {
resolve(result);
},
reject
);
});
}
// This is only for a fallback
// If there is a LbryFirst method that is being called by an app, it should be added to /flow-typed/LbryFirst.js
const lbryFirstProxy = new Proxy(LbryFirst, {
get(target: LbryFirstTypes, name: string) {
if (name in target) {
return target[name];
}
return (params = {}) =>
new Promise((resolve, reject) => {
apiCall(name, params, resolve, reject);
});
},
});
export default lbryFirstProxy;

View file

@ -1,3 +0,0 @@
import Recsys from './recsys';
export default Recsys;

View file

@ -1,257 +0,0 @@
import { selectUser } from 'redux/selectors/user';
import { makeSelectRecommendedRecsysIdForClaimId } from 'redux/selectors/search';
import { v4 as Uuidv4 } from 'uuid';
import { parseURI } from 'util/lbryURI';
import * as SETTINGS from 'constants/settings';
import { makeSelectClaimForUri } from 'redux/selectors/claims';
import { selectPlayingUri, selectPrimaryUri } from 'redux/selectors/content';
import { makeSelectClientSetting, selectDaemonSettings } from 'redux/selectors/settings';
import { history } from 'ui/store';
const recsysEndpoint = 'https://clickstream.odysee.com/log/video/view';
const recsysId = 'lighthouse-v0';
const getClaimIdsFromUris = (uris) => {
return uris
? uris.map((uri) => {
try {
const { claimId } = parseURI(uri);
return claimId;
} catch (e) {
return [];
}
})
: [];
};
const recsys = {
entries: {},
debug: false,
/**
* Provides for creating, updating, and sending Clickstream data object Entries.
* Entries are Created either when recommendedContent loads, or when recommendedContent is clicked.
* If recommended content is clicked, An Entry with parentUuid is created.
* On page load, find an empty entry with your claimId, or create a new entry and record to it.
* The entry will be populated with the following:
* - parentUuid // optional
* - Uuid
* - claimId
* - recommendedClaims [] // optionally empty
* - playerEvents [] // optionally empty
* - recommendedClaimsIndexClicked [] // optionally empty
* - UserId
* - pageLoadedAt
* - isEmbed
* - pageExitedAt
* - recsysId // optional
*/
/**
* Function: onClickedRecommended()
* Called when RecommendedContent was clicked.
* Adds index of clicked recommendation to parent entry
* Adds new Entry with parentUuid for destination page
* @param parentClaimId: string,
* @param newClaimId: string,
*/
onClickedRecommended: function (parentClaimId, newClaimId) {
const parentEntry = recsys.entries[parentClaimId] ? recsys.entries[parentClaimId] : null;
const parentUuid = parentEntry['uuid'];
const parentRecommendedClaims = parentEntry['recClaimIds'] || [];
const parentClickedIndexes = parentEntry['recClickedVideoIdx'] || [];
const indexClicked = parentRecommendedClaims.indexOf(newClaimId);
if (parentUuid) {
recsys.createRecsysEntry(newClaimId, parentUuid);
}
parentClickedIndexes.push(indexClicked);
recsys.log('onClickedRecommended', { parentClaimId, newClaimId });
},
/**
* Page was loaded. Get or Create entry and populate it with default data, plus recommended content, recsysId, etc.
* Called from recommendedContent component
*/
onRecsLoaded: function (claimId, uris) {
if (window.store) {
const state = window.store.getState();
if (!recsys.entries[claimId]) {
recsys.createRecsysEntry(claimId);
}
const claimIds = getClaimIdsFromUris(uris);
recsys.entries[claimId]['recsysId'] = makeSelectRecommendedRecsysIdForClaimId(claimId)(state) || recsysId;
recsys.entries[claimId]['pageLoadedAt'] = Date.now();
recsys.entries[claimId]['recClaimIds'] = claimIds;
}
recsys.log('onRecsLoaded', claimId);
},
/**
* Creates an Entry with optional parentUuid
* @param: claimId: string
* @param: parentUuid: string (optional)
*/
createRecsysEntry: function (claimId, parentUuid) {
if (window.store && claimId) {
const state = window.store.getState();
const user = selectUser(state);
const userId = user ? user.id : null;
if (parentUuid) {
// Make a stub entry that will be filled out on page load
recsys.entries[claimId] = {
uuid: Uuidv4(),
parentUuid: parentUuid,
uid: userId || null, // selectUser
claimId: claimId,
recClickedVideoIdx: [],
pageLoadedAt: Date.now(),
events: [],
};
} else {
recsys.entries[claimId] = {
uuid: Uuidv4(),
uid: userId, // selectUser
claimId: claimId,
pageLoadedAt: Date.now(),
recsysId: null,
recClaimIds: [],
recClickedVideoIdx: [],
events: [],
};
}
}
recsys.log('createRecsysEntry', claimId);
},
/**
* Send event for claimId
* @param claimId
* @param isTentative
*/
sendRecsysEntry: function (claimId, isTentative) {
const shareTelemetry =
IS_WEB || (window && window.store && selectDaemonSettings(window.store.getState()).share_usage_data);
if (recsys.entries[claimId] && shareTelemetry) {
const data = JSON.stringify(recsys.entries[claimId]);
try {
navigator.sendBeacon(recsysEndpoint, data);
if (!isTentative) {
delete recsys.entries[claimId];
}
} catch (error) {
console.log('no beacon for you', error);
}
}
recsys.log('sendRecsysEntry', claimId);
},
/**
* A player event fired. Get the Entry for the claimId, and add the events
* @param claimId
* @param event
*/
onRecsysPlayerEvent: function (claimId, event, isEmbedded) {
if (!recsys.entries[claimId]) {
recsys.createRecsysEntry(claimId);
// do something to show it's floating or autoplay
}
if (isEmbedded) {
recsys.entries[claimId]['isEmbed'] = true;
}
recsys.entries[claimId].events.push(event);
recsys.log('onRecsysPlayerEvent', claimId);
},
log: function (callName, claimId) {
if (recsys.debug) {
console.log(`Call: ***${callName}***, ClaimId: ${claimId}, Recsys Entries`, Object.assign({}, recsys.entries));
}
},
/**
* Player closed. Check to see if primaryUri = playingUri
* if so, send the Entry.
*/
onPlayerDispose: function (claimId, isEmbedded) {
if (window.store) {
const state = window.store.getState();
const playingUri = selectPlayingUri(state);
const primaryUri = selectPrimaryUri(state);
const onFilePage = playingUri === primaryUri;
if (!onFilePage || isEmbedded) {
if (isEmbedded) {
recsys.entries[claimId]['isEmbed'] = true;
}
recsys.sendRecsysEntry(claimId);
}
}
recsys.log('PlayerDispose', claimId);
},
// /**
// * File page unmount or change event
// * Check to see if playingUri, floatingEnabled, primaryUri === playingUri
// * If not, send the Entry.
// * If floating enabled, leaving file page will pop out player, leading to
// * more events until player is disposed. Don't send unless floatingPlayer playingUri
// */
// onLeaveFilePage: function (primaryUri) {
// if (window.store) {
// const state = window.store.getState();
// const claim = makeSelectClaimForUri(primaryUri)(state);
// const claimId = claim ? claim.claim_id : null;
// const playingUri = selectPlayingUri(state);
// const actualPlayingUri = playingUri && playingUri.uri;
// // const primaryUri = selectPrimaryUri(state);
// const floatingPlayer = makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state);
// // When leaving page, if floating player is enabled, play will continue.
// if (claimId) {
// recsys.entries[claimId]['pageExitedAt'] = Date.now();
// }
// const shouldSend =
// (claimId && floatingPlayer && actualPlayingUri && actualPlayingUri !== primaryUri) || !floatingPlayer || !actualPlayingUri;
// if (shouldSend) {
// recsys.sendRecsysEntry(claimId);
// }
// recsys.log('LeaveFile', claimId);
// }
// },
/**
* Navigate event
* Send all claimIds that aren't currently playing.
*/
onNavigate: function () {
if (window.store) {
const state = window.store.getState();
const playingUri = selectPlayingUri(state);
const actualPlayingUri = playingUri && playingUri.uri;
const claim = makeSelectClaimForUri(actualPlayingUri)(state);
const playingClaimId = claim ? claim.claim_id : null;
// const primaryUri = selectPrimaryUri(state);
const floatingPlayer = makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state);
// When leaving page, if floating player is enabled, play will continue.
Object.keys(recsys.entries).forEach((claimId) => {
const shouldSkip = recsys.entries[claimId].parentUuid && !recsys.entries[claimId].recClaimIds;
if (!shouldSkip && ((claimId !== playingClaimId && floatingPlayer) || !floatingPlayer)) {
recsys.entries[claimId]['pageExitedAt'] = Date.now();
recsys.sendRecsysEntry(claimId);
}
recsys.log('OnNavigate', claimId);
});
}
},
};
// @if TARGET='web'
document.addEventListener('visibilitychange', function logData() {
if (document.visibilityState === 'hidden') {
Object.keys(recsys.entries).map((claimId) => recsys.sendRecsysEntry(claimId, true));
}
});
// @endif
history.listen(() => {
recsys.onNavigate();
});
export default recsys;

View file

@ -51,7 +51,6 @@ type Analytics = {
) => Promise<any>,
emailProvidedEvent: () => void,
emailVerifiedEvent: () => void,
rewardEligibleEvent: () => void,
startupEvent: () => void,
purchaseEvent: (number) => void,
readyEvent: (number) => void,
@ -355,9 +354,6 @@ const analytics: Analytics = {
emailVerifiedEvent: () => {
sendMatomoEvent('Engagement', 'Email-Verified');
},
rewardEligibleEvent: () => {
sendMatomoEvent('Engagement', 'Reward-Eligible');
},
openUrlEvent: (url: string) => {
sendMatomoEvent('Engagement', 'Open-Url', url);
},

View file

@ -1,9 +1,6 @@
import { hot } from 'react-hot-loader/root';
import { connect } from 'react-redux';
import { selectGetSyncErrorMessage, selectSyncFatalError } from 'redux/selectors/sync';
import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
import { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims';
import { selectMyChannelUrls, selectMyChannelClaimIds } from 'redux/selectors/claims';
import * as SETTINGS from 'constants/settings';
@ -25,7 +22,7 @@ import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings
import { doSyncLoop } from 'redux/actions/sync';
import {
doDownloadUpgradeRequested,
doSignIn,
doSignIn, // huh
doGetAndPopulatePreferences,
doSetActiveChannel,
doSetIncognito,
@ -34,8 +31,6 @@ import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/c
import App from './view';
const select = (state) => ({
user: selectUser(state),
accessToken: selectAccessToken(state),
theme: selectThemePath(state),
language: selectLanguage(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
@ -43,8 +38,6 @@ const select = (state) => ({
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state),
syncError: selectGetSyncErrorMessage(state),
rewards: selectUnclaimedRewards(state),
isAuthenticated: selectUserVerifiedEmail(state),
currentModal: selectModal(state),
syncFatalError: selectSyncFatalError(state),
activeChannelClaim: selectActiveChannelClaim(state),
@ -55,7 +48,6 @@ const select = (state) => ({
});
const perform = (dispatch) => ({
fetchAccessToken: () => dispatch(doFetchAccessToken()),
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
fetchCollectionListMine: () => dispatch(doFetchCollectionListMine()),
setLanguage: (language) => dispatch(doSetLanguage(language)),
@ -64,7 +56,6 @@ const perform = (dispatch) => ({
updatePreferences: () => dispatch(doGetAndPopulatePreferences()),
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
syncLoop: (noInterval) => dispatch(doSyncLoop(noInterval)),
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()),
setIncognito: () => dispatch(doSetIncognito()),
fetchModBlockedList: () => dispatch(doFetchModBlockedList()),

View file

@ -1,16 +1,12 @@
// @flow
import * as PAGES from 'constants/pages';
import React, { useEffect, useRef, useState, useLayoutEffect } from 'react';
import classnames from 'classnames';
import analytics from 'analytics';
import Router from 'component/router/index';
import ReactModal from 'react-modal';
import { openContextMenu } from 'util/context-menu';
import useKonamiListener from 'util/enhanced-layout';
import FileRenderFloating from 'component/fileRenderFloating';
import { withRouter } from 'react-router';
import usePrevious from 'effects/use-previous';
import REWARDS from 'rewards';
import usePersistedState from 'effects/use-persisted-state';
import LANGUAGES from 'constants/languages';
import useZoom from 'effects/use-zoom';
@ -46,7 +42,6 @@ type Props = {
length: number,
push: (string) => void,
},
fetchAccessToken: () => void,
fetchChannelListMine: () => void,
fetchCollectionListMine: () => void,
signIn: () => void,
@ -82,27 +77,18 @@ type Props = {
function App(props: Props) {
const {
theme,
user,
fetchAccessToken,
fetchChannelListMine,
fetchCollectionListMine,
signIn,
autoUpdateDownloaded,
isUpgradeAvailable,
requestDownloadUpgrade,
uploadCount,
history,
syncError,
language,
languages,
setLanguage,
updatePreferences,
getWalletSyncPref,
rewards,
setReferrer,
isAuthenticated,
syncLoop,
currentModal,
syncFatalError,
myChannelClaimIds,
activeChannelId,
@ -117,38 +103,16 @@ function App(props: Props) {
const appRef = useRef();
const isEnhancedLayout = useKonamiListener();
const [hasSignedIn, setHasSignedIn] = useState(false);
const [readyForSync, setReadyForSync] = useState(false);
const [readyForPrefs, setReadyForPrefs] = useState(false);
const hasVerifiedEmail = user && Boolean(user.has_verified_email);
const isRewardApproved = user && user.is_reward_approved;
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
const previousRewardApproved = usePrevious(isRewardApproved);
const { pathname, search } = props.location;
const [upgradeNagClosed, setUpgradeNagClosed] = useState(false);
const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false);
// const [retryingSync, setRetryingSync] = useState(false);
const [langRenderKey, setLangRenderKey] = useState(0);
const [sidebarOpen] = usePersistedState('sidebar', true);
const showUpgradeButton = (autoUpdateDownloaded || isUpgradeAvailable) && !upgradeNagClosed;
// referral claiming
const referredRewardAvailable = rewards && rewards.some((reward) => reward.reward_type === REWARDS.TYPE_REFEREE);
const urlParams = new URLSearchParams(search);
const rawReferrerParam = urlParams.get('r');
const sanitizedReferrerParam = rawReferrerParam && rawReferrerParam.replace(':', '#');
const userId = user && user.id;
const useCustomScrollbar = !IS_MAC;
const hasMyChannels = myChannelClaimIds && myChannelClaimIds.length > 0;
const hasNoChannels = myChannelClaimIds && myChannelClaimIds.length === 0;
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
const hasActiveChannelClaim = activeChannelId !== undefined;
const isPersonalized = hasVerifiedEmail;
useEffect(() => {
if (userId) {
analytics.setUser(userId);
}
}, [userId]);
useEffect(() => {
if (!uploadCount) return;
@ -188,23 +152,10 @@ function App(props: Props) {
}, []);
// Enable ctrl +/- zooming on Desktop.
// @if TARGET='app'
useZoom();
// @endif
// Enable 'Alt + Left/Right' for history navigation on Desktop.
// @if TARGET='app'
useHistoryNav(history);
// @endif
useEffect(() => {
if (referredRewardAvailable && sanitizedReferrerParam && isRewardApproved) {
setReferrer(sanitizedReferrerParam, true);
} else if (referredRewardAvailable && sanitizedReferrerParam) {
setReferrer(sanitizedReferrerParam, false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sanitizedReferrerParam, isRewardApproved, referredRewardAvailable]);
useEffect(() => {
const { current: wrapperElement } = appRef;
@ -212,13 +163,9 @@ function App(props: Props) {
ReactModal.setAppElement(wrapperElement);
}
fetchAccessToken();
// @if TARGET='app'
fetchChannelListMine(); // This is fetched after a user is signed in on web
fetchCollectionListMine();
// @endif
}, [appRef, fetchAccessToken, fetchChannelListMine, fetchCollectionListMine]);
}, [appRef, fetchChannelListMine, fetchCollectionListMine]);
useEffect(() => {
// $FlowFixMe
@ -261,71 +208,20 @@ function App(props: Props) {
}, [shouldMigrateLanguage, setLanguage]);
useEffect(() => {
// Check that previousHasVerifiedEmail was not undefined instead of just not truthy
// This ensures we don't fire the emailVerified event on the initial user fetch
if (previousHasVerifiedEmail === false && hasVerifiedEmail) {
analytics.emailVerifiedEvent();
if (updatePreferences && getWalletSyncPref) {
getWalletSyncPref().then(() => updatePreferences());
}
}, [previousHasVerifiedEmail, hasVerifiedEmail, signIn]);
useEffect(() => {
if (previousRewardApproved === false && isRewardApproved) {
analytics.rewardEligibleEvent();
}
}, [previousRewardApproved, isRewardApproved]);
useEffect(() => {
if (updatePreferences && getWalletSyncPref && readyForPrefs) {
getWalletSyncPref()
.then(() => updatePreferences())
.then(() => {
setReadyForSync(true);
});
}
}, [updatePreferences, getWalletSyncPref, setReadyForSync, readyForPrefs, hasVerifiedEmail]);
// ready for sync syncs, however after signin when hasVerifiedEmail, that syncs too.
useEffect(() => {
// signInSyncPref is cleared after sharedState loop.
if (readyForSync && hasVerifiedEmail) {
// In case we are syncing.
syncLoop();
}
}, [readyForSync, hasVerifiedEmail, syncLoop]);
// We know someone is logging in or not when we get their user object
// We'll use this to determine when it's time to pull preferences
// This will no longer work if desktop users no longer get a user object from lbryinc
useEffect(() => {
if (user) {
setReadyForPrefs(true);
}
}, [user, setReadyForPrefs]);
useEffect(() => {
if (syncError && isAuthenticated && !pathname.includes(PAGES.AUTH_WALLET_PASSWORD) && !currentModal) {
history.push(`/$/${PAGES.AUTH_WALLET_PASSWORD}?redirect=${pathname}`);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [syncError, pathname, isAuthenticated]);
// Keep this at the end to ensure initial setup effects are run first
useEffect(() => {
if (!hasSignedIn && hasVerifiedEmail) {
signIn();
setHasSignedIn(true);
}
}, [hasVerifiedEmail, signIn, hasSignedIn]);
}, [updatePreferences, getWalletSyncPref]);
// batch resolve subscriptions to be used by the sideNavigation component.
// add it here so that it only resolves the first time, despite route changes.
// useLayoutEffect because it has to be executed before the sideNavigation component requests them
useLayoutEffect(() => {
if (sidebarOpen && isPersonalized && subscriptions && !resolvedSubscriptions) {
if (sidebarOpen && subscriptions && !resolvedSubscriptions) {
setResolvedSubscriptions(true);
resolveUris(subscriptions.map((sub) => sub.uri));
}
}, [sidebarOpen, isPersonalized, resolvedSubscriptions, subscriptions, resolveUris, setResolvedSubscriptions]);
}, [sidebarOpen, resolvedSubscriptions, subscriptions, resolveUris, setResolvedSubscriptions]);
useEffect(() => {
// When language is changed or translations are fetched, we render.

View file

@ -1,12 +1,9 @@
import Button from './view';
import React, { forwardRef } from 'react';
import { connect } from 'react-redux';
import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
const mapStateToProps = (state) => ({
pathname: state.router.location.pathname,
emailVerified: selectUserVerifiedEmail(state),
user: selectUser(state),
});
const ConnectedButton = connect(mapStateToProps)(Button);

View file

@ -32,11 +32,9 @@ type Props = {
onMouseEnter: ?(any) => any,
onMouseLeave: ?(any) => any,
pathname: string,
emailVerified: boolean,
myref: any,
dispatch: any,
'aria-label'?: string,
user: ?User,
};
// use forwardRef to allow consumers to pass refs to the button content if they want to
@ -63,11 +61,9 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
iconSize,
iconColor,
activeClass,
emailVerified,
myref,
dispatch, // <button> doesn't know what to do with dispatch
pathname,
user,
authSrc,
...otherProps
} = props;

View file

@ -1,11 +0,0 @@
import { connect } from 'react-redux';
import { selectUserEmail } from 'redux/selectors/user';
import CardVerify from './view';
const select = state => ({
email: selectUserEmail(state),
});
const perform = () => ({});
export default connect(select, perform)(CardVerify);

View file

@ -1,185 +0,0 @@
/* eslint-disable no-undef */
/* eslint-disable react/prop-types */
import React from 'react';
import Button from 'component/button';
let scriptLoading = false;
let scriptLoaded = false;
let scriptDidError = false;
// Flow does not like the way this stripe plugin works
// Disabled because it was a huge pain
// type Props = {
// disabled: boolean,
// label: ?string,
// email: string,
// // =====================================================
// // Required by stripe
// // see Stripe docs for more info:
// // https://stripe.com/docs/checkout#integration-custom
// // =====================================================
// // Your publishable key (test or live).
// // can't use "key" as a prop in react, so have to change the keyname
// stripeKey: string,
// // The callback to invoke when the Checkout process is complete.
// // function(token)
// // token is the token object created.
// // token.id can be used to create a charge or customer.
// // token.email contains the email address entered by the user.
// token: string,
// };
class CardVerify extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
scriptFailedToLoad: false,
};
}
componentDidMount() {
if (scriptLoaded) {
return;
}
if (scriptLoading) {
return;
}
scriptLoading = true;
const script = document.createElement('script');
script.src = 'https://checkout.stripe.com/checkout.js';
script.async = true;
this.loadPromise = (() => {
let canceled = false;
const promise = new Promise((resolve, reject) => {
script.onload = () => {
scriptLoaded = true;
scriptLoading = false;
resolve();
this.onScriptLoaded();
};
script.onerror = event => {
scriptDidError = true;
scriptLoading = false;
reject(event);
this.onScriptError(event);
};
});
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(() => (canceled ? reject({ isCanceled: true }) : resolve()));
promise.catch(error => (canceled ? reject({ isCanceled: true }) : reject(error)));
});
return {
promise: wrappedPromise,
reject() {
canceled = true;
},
};
})();
this.loadPromise.promise.then(this.onScriptLoaded).catch(this.onScriptError);
// $FlowFixMe
document.body.appendChild(script);
}
componentDidUpdate() {
if (!scriptLoading) {
this.updateStripeHandler();
}
}
componentWillUnmount() {
if (this.loadPromise) {
this.loadPromise.reject();
}
if (CardVerify.stripeHandler && this.state.open) {
CardVerify.stripeHandler.close();
}
}
onScriptLoaded = () => {
if (!CardVerify.stripeHandler) {
CardVerify.stripeHandler = StripeCheckout.configure({
key: this.props.stripeKey,
});
if (this.hasPendingClick) {
this.showStripeDialog();
}
}
};
onScriptError = (...args) => {
this.setState({ scriptFailedToLoad: true });
};
onClosed = () => {
this.setState({ open: false });
};
updateStripeHandler() {
if (!CardVerify.stripeHandler) {
CardVerify.stripeHandler = StripeCheckout.configure({
key: this.props.stripeKey,
});
}
}
showStripeDialog() {
this.setState({ open: true });
CardVerify.stripeHandler.open({
allowRememberMe: false,
closed: this.onClosed,
description: __('Confirm Identity'),
email: this.props.email,
locale: 'auto',
panelLabel: 'Verify',
token: this.props.token,
zipCode: true,
});
}
onClick = () => {
if (scriptDidError) {
try {
throw new Error('Tried to call onClick, but StripeCheckout failed to load');
} catch (x) {}
} else if (CardVerify.stripeHandler) {
this.showStripeDialog();
} else {
this.hasPendingClick = true;
}
};
render() {
const { scriptFailedToLoad } = this.props;
return (
<div>
{scriptFailedToLoad && (
<div className="error__text">There was an error connecting to Stripe. Please try again later.</div>
)}
<Button
button="primary"
label={this.props.label}
disabled={this.props.disabled || this.state.open || this.hasPendingClick}
onClick={this.onClick.bind(this)}
/>
</div>
);
}
}
export default CardVerify;
/* eslint-enable no-undef */
/* eslint-enable react/prop-types */

View file

@ -11,7 +11,6 @@ import { doResolveUris } from 'redux/actions/claims';
import * as SETTINGS from 'constants/settings';
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { withRouter } from 'react-router';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { makeSelectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
import ChannelContent from './view';
@ -29,7 +28,6 @@ const select = (state, props) => {
channelIsMine: selectClaimIsMine(state, claim),
channelIsBlocked: makeSelectChannelIsMuted(props.uri)(state),
claim,
isAuthenticated: selectUserVerifiedEmail(state),
showMature: selectShowMatureContent(state),
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
};

View file

@ -26,7 +26,6 @@ type Props = {
defaultPageSize?: number,
defaultInfiniteScroll?: Boolean,
claim: Claim,
isAuthenticated: boolean,
showMature: boolean,
tileLayout: boolean,
viewHiddenChannels: boolean,

View file

@ -15,8 +15,6 @@ import { selectBalance } from 'redux/selectors/wallet';
import { doUpdateChannel, doCreateChannel, doClearChannelErrors } from 'redux/actions/claims';
import { doOpenModal } from 'redux/actions/app';
import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments';
import { doClaimInitialRewards } from 'redux/actions/rewards';
import { selectIsClaimingInitialRewards, selectHasClaimedInitialRewards } from 'redux/selectors/rewards';
import ChannelForm from './view';
const select = (state, props) => ({
@ -36,8 +34,6 @@ const select = (state, props) => ({
createError: selectCreateChannelError(state),
creatingChannel: selectCreatingChannel(state),
balance: selectBalance(state),
isClaimingInitialRewards: selectIsClaimingInitialRewards(state),
hasClaimedInitialRewards: selectHasClaimedInitialRewards(state),
});
const perform = (dispatch) => ({
@ -52,7 +48,6 @@ const perform = (dispatch) => ({
);
},
clearChannelErrors: () => dispatch(doClearChannelErrors()),
claimInitialRewards: () => dispatch(doClaimInitialRewards()),
});
export default connect(select, perform)(ChannelForm);

View file

@ -51,7 +51,6 @@ type Props = {
createError: string,
creatingChannel: boolean,
clearChannelErrors: () => void,
claimInitialRewards: () => void,
onDone: () => void,
openModal: (
id: string,
@ -59,8 +58,6 @@ type Props = {
) => void,
uri: string,
disabled: boolean,
isClaimingInitialRewards: boolean,
hasClaimedInitialRewards: boolean,
};
function ChannelForm(props: Props) {
@ -85,11 +82,8 @@ function ChannelForm(props: Props) {
creatingChannel,
createError,
clearChannelErrors,
claimInitialRewards,
openModal,
disabled,
isClaimingInitialRewards,
hasClaimedInitialRewards,
} = props;
const [nameError, setNameError] = React.useState(undefined);
const [bidError, setBidError] = React.useState('');
@ -107,21 +101,11 @@ function ChannelForm(props: Props) {
const primaryLanguage = Array.isArray(languageParam) && languageParam.length && languageParam[0];
const secondaryLanguage = Array.isArray(languageParam) && languageParam.length >= 2 && languageParam[1];
const submitLabel = React.useMemo(() => {
if (isClaimingInitialRewards) {
return __('Claiming credits...');
}
return creatingChannel || updatingChannel ? __('Submitting...') : __('Submit');
}, [isClaimingInitialRewards, creatingChannel, updatingChannel]);
}, [creatingChannel, updatingChannel]);
const submitDisabled = React.useMemo(() => {
return (
isClaimingInitialRewards ||
creatingChannel ||
updatingChannel ||
coverError ||
bidError ||
(isNewChannel && !params.name)
);
}, [isClaimingInitialRewards, creatingChannel, updatingChannel, nameError, bidError, isNewChannel, params.name]);
return creatingChannel || updatingChannel || coverError || bidError || (isNewChannel && !params.name);
}, [creatingChannel, updatingChannel, nameError, bidError, isNewChannel, params.name]);
function getChannelParams() {
// fill this in with sdk data
@ -255,12 +239,6 @@ function ChannelForm(props: Props) {
clearChannelErrors();
}, [clearChannelErrors]);
React.useEffect(() => {
if (!hasClaimedInitialRewards) {
claimInitialRewards();
}
}, [hasClaimedInitialRewards, claimInitialRewards]);
const coverSrc = coverError ? ThumbnailBrokenImage : coverPreview;
let thumbnailPreview;

View file

@ -10,7 +10,6 @@ import { doCommentUpdate, doCommentList } from 'redux/actions/comments';
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { doToast } from 'redux/actions/notifications';
import { doClearPlayingUri } from 'redux/actions/content';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import {
selectLinkedCommentAncestors,
selectOthersReactsForComment,
@ -33,7 +32,6 @@ const select = (state, props) => {
claim: makeSelectClaimForUri(uri)(state),
thumbnail: author_uri && selectThumbnailForUri(state, author_uri),
channelIsBlocked: author_uri && makeSelectChannelIsMuted(author_uri)(state),
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
othersReacts: selectOthersReactsForComment(state, reactionKey),
activeChannelClaim,
hasChannels: selectHasChannels(state),

View file

@ -49,7 +49,6 @@ type Props = {
linkedCommentId?: string,
linkedCommentAncestors: { [string]: Array<string> },
hasChannels: boolean,
commentingEnabled: boolean,
doToast: ({ message: string }) => void,
isTopLevel?: boolean,
threadDepth: number,
@ -82,7 +81,6 @@ function CommentView(props: Props) {
totalReplyPages,
linkedCommentId,
linkedCommentAncestors,
commentingEnabled,
hasChannels,
doToast,
isTopLevel,
@ -368,7 +366,7 @@ function CommentView(props: Props) {
<div className="comment__actions">
{threadDepth !== 0 && (
<Button
label={commentingEnabled ? __('Reply') : __('Log in to reply')}
label={__('Reply')}
className="comment__action"
onClick={handleCommentReply}
icon={ICONS.REPLY}

View file

@ -157,31 +157,6 @@ export default function CreatorAnalytics(props: Props) {
/>
</div>
{/* <Card
iconColor
className="section"
title={<span>{__('%lbc_received% LBRY Credits Earned', { lbc_received: stats.AllLBCReceived })}</span>}
icon={ICONS.REWARDS}
subtitle={
<React.Fragment>
<div className="card__data-subtitle">
<span>
{'+'}{' '}
{__('%lbc_received_changed% this week', {
lbc_received_changed: stats.LBCReceivedChange || 0,
})}
</span>
{stats.LBCReceivedChange > 0 && <Icon icon={ICONS.TRENDING} iconColor="green" size={18} />}
</div>
<p className="help">
{__(
"Earnings may also include any LBC you've sent yourself or added as support. We are working on making this more accurate. Check your wallet page for the correct total balance."
)}
</p>
</React.Fragment>
}
/> */}
{stats.VideoURITopNew ? (
<Card
className="section"

View file

@ -1,20 +0,0 @@
import { connect } from 'react-redux';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectEmailToVerify, selectUser } from 'redux/selectors/user';
import FirstRunEmailCollection from './view';
import * as SETTINGS from 'constants/settings';
const select = (state) => ({
emailCollectionAcknowledged: makeSelectClientSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED)(state),
email: selectEmailToVerify(state),
user: selectUser(state),
});
const perform = (dispatch) => () => ({
acknowledgeEmail: () => {
dispatch(doSetClientSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED, true));
},
});
export default connect(select, perform)(FirstRunEmailCollection);

View file

@ -1,40 +0,0 @@
// @flow
import React from 'react';
import Button from 'component/button';
import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify';
type Props = {
email: string,
emailCollectionAcknowledged: boolean,
user: ?{ has_verified_email: boolean },
completeFirstRun: () => void,
acknowledgeEmail: () => void,
};
class FirstRunEmailCollection extends React.PureComponent<Props> {
render() {
const { completeFirstRun, email, user, emailCollectionAcknowledged, acknowledgeEmail } = this.props;
// this shouldn't happen
if (!user) {
return null;
}
const cancelButton = <Button button="link" onClick={completeFirstRun} label={__('Not Now')} />;
if (user && !user.has_verified_email && !email) {
return <UserEmailNew cancelButton={cancelButton} />;
} else if (user && !user.has_verified_email) {
return <UserEmailVerify cancelButton={cancelButton} />;
}
// Try to acknowledge here so users don't see an empty email screen in the first run banner
if (!emailCollectionAcknowledged) {
acknowledgeEmail();
}
return null;
}
}
export default FirstRunEmailCollection;

View file

@ -1,7 +1,6 @@
import { connect } from 'react-redux';
import { makeSelectClaimForUri, makeSelectContentTypeForUri, makeSelectMetadataForUri } from 'redux/selectors/claims';
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
import { selectUser } from 'redux/selectors/user';
import { doOpenFileInFolder } from 'redux/actions/file';
import FileDetails from './view';
@ -10,7 +9,6 @@ const select = (state, props) => ({
contentType: makeSelectContentTypeForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state),
user: selectUser(state),
});
const perform = (dispatch) => ({

View file

@ -10,7 +10,6 @@ type Props = {
metadata: StreamMetadata,
openFolder: (string) => void,
contentType: string,
user: ?any,
};
class FileDetails extends PureComponent<Props> {

View file

@ -1,7 +1,6 @@
import { connect } from 'react-redux';
import { makeSelectFileInfoForUri, makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
import { makeSelectClaimWasPurchased } from 'redux/selectors/claims';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
import { makeSelectFileRenderModeForUri, selectPrimaryUri } from 'redux/selectors/content';
import { withRouter } from 'react-router';
import { doAnalyticsView } from 'redux/actions/app';
@ -19,7 +18,6 @@ const select = (state, props) => ({
const perform = (dispatch) => ({
triggerAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
});
export default withRouter(connect(select, perform)(FileRenderInline));

View file

@ -11,23 +11,13 @@ type Props = {
renderMode: string,
streamingUrl?: string,
triggerAnalyticsView: (string, number) => Promise<any>,
claimRewards: () => void,
costInfo: any,
claimWasPurchased: boolean,
};
export default function FileRenderInline(props: Props) {
const {
isPlaying,
fileInfo,
uri,
streamingUrl,
triggerAnalyticsView,
claimRewards,
renderMode,
costInfo,
claimWasPurchased,
} = props;
const { isPlaying, fileInfo, uri, streamingUrl, triggerAnalyticsView, renderMode, costInfo, claimWasPurchased } =
props;
const [playTime, setPlayTime] = useState();
const isFree = !costInfo || (costInfo.cost !== undefined && costInfo.cost === 0);
const isReadyToView = fileInfo && fileInfo.completed;
@ -55,11 +45,10 @@ export default function FileRenderInline(props: Props) {
const timeToStart = Date.now() - playTime;
triggerAnalyticsView(uri, timeToStart).then(() => {
claimRewards();
setPlayTime(null);
});
}
}, [setPlayTime, claimRewards, triggerAnalyticsView, isReadyToPlay, playTime, uri]);
}, [setPlayTime, triggerAnalyticsView, isReadyToPlay, playTime, uri]);
if (!isPlaying) {
return null;

View file

@ -1,29 +1,23 @@
import { connect } from 'react-redux';
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
import { doSignOut } from 'redux/actions/app';
import { formatCredits } from 'util/format-credits';
import { selectClientSetting } from 'redux/selectors/settings';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { selectHasNavigated } from 'redux/selectors/app';
import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet';
import { selectEmailToVerify, selectUser } from 'redux/selectors/user';
import * as SETTINGS from 'constants/settings';
import Header from './view';
const select = (state) => ({
balance: selectBalance(state),
emailToVerify: selectEmailToVerify(state),
hasNavigated: selectHasNavigated(state),
hideBalance: selectClientSetting(state, SETTINGS.HIDE_BALANCE),
roundedBalance: formatCredits(selectTotalBalance(state), 2, true),
roundedSpendableBalance: formatCredits(selectBalance(state), 2, true),
syncError: selectGetSyncErrorMessage(state),
user: selectUser(state),
});
const perform = (dispatch) => ({
clearEmailEntry: () => dispatch(doClearEmailEntry()),
clearPasswordEntry: () => dispatch(doClearPasswordEntry()),
signOut: () => dispatch(doSignOut()),
});

View file

@ -28,7 +28,6 @@ type Props = {
simpleTitle: string, // Just use the same value as `title` if `title` is already short (~< 10 chars), unless you have a better idea for title overlfow on mobile
},
balance: number,
emailToVerify?: string,
hasNavigated: boolean,
hideBalance: boolean,
hideCancel: boolean,
@ -43,8 +42,6 @@ type Props = {
roundedSpendableBalance: string,
sidebarOpen: boolean,
syncError: ?string,
clearEmailEntry: () => void,
clearPasswordEntry: () => void,
setSidebarOpen: (boolean) => void,
signOut: () => void,
};
@ -54,7 +51,6 @@ const Header = (props: Props) => {
authHeader,
backout,
balance,
emailToVerify,
hideBalance,
hideCancel,
history,
@ -63,8 +59,6 @@ const Header = (props: Props) => {
roundedSpendableBalance,
sidebarOpen,
syncError,
clearEmailEntry,
clearPasswordEntry,
setSidebarOpen,
signOut,
} = props;
@ -80,7 +74,6 @@ const Header = (props: Props) => {
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
const isVerifyPage = pathname.includes(PAGES.AUTH_VERIFY);
const isSignUpPage = pathname.includes(PAGES.AUTH);
const isSignInPage = pathname.includes(PAGES.AUTH_SIGNIN);
const isPwdResetPage = pathname.includes(PAGES.AUTH_PASSWORD_RESET);
// For pages that allow for "backing out", shows a backout option instead of the Home logo
@ -236,12 +229,9 @@ const Header = (props: Props) => {
// className="button--header-close"
icon={ICONS.REMOVE}
onClick={() => {
clearEmailEntry();
clearPasswordEntry();
if (syncError) signOut();
if ((isSignInPage && !emailToVerify) || isSignUpPage || isPwdResetPage) {
if (isSignUpPage || isPwdResetPage) {
goBack();
} else {
push('/');

View file

@ -4,13 +4,11 @@ import { selectActiveChannelStakedLevel } from 'redux/selectors/app';
import { selectClientSetting } from 'redux/selectors/settings';
import * as SETTINGS from 'constants/settings';
import HeaderMenuButtons from './view';
import { selectUser } from 'redux/selectors/user';
const select = (state) => ({
activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
automaticDarkModeEnabled: selectClientSetting(state, SETTINGS.AUTOMATIC_DARK_MODE_ENABLED),
currentTheme: selectClientSetting(state, SETTINGS.THEME),
user: selectUser(state),
});
const perform = (dispatch) => ({

View file

@ -14,14 +14,13 @@ import Tooltip from 'component/common/tooltip';
type HeaderMenuButtonProps = {
automaticDarkModeEnabled: boolean,
currentTheme: string,
user: ?User,
handleThemeToggle: (boolean, string) => void,
};
export default function HeaderMenuButtons(props: HeaderMenuButtonProps) {
const { automaticDarkModeEnabled, currentTheme, user, handleThemeToggle } = props;
const { automaticDarkModeEnabled, currentTheme, handleThemeToggle } = props;
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS;
return (
<div className="header__buttons">

View file

@ -1,12 +1,10 @@
import { connect } from 'react-redux';
import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
import { doSeeAllNotifications } from 'redux/actions/notifications';
import { selectUser } from 'redux/selectors/user';
import NotificationHeaderButton from './view';
const select = (state) => ({
unseenCount: selectUnseenNotificationCount(state),
user: selectUser(state),
});
export default connect(select, {

View file

@ -13,15 +13,14 @@ import Tooltip from 'component/common/tooltip';
type Props = {
unseenCount: number,
user: ?User,
doSeeAllNotifications: () => void,
};
export default function NotificationHeaderButton(props: Props) {
const { unseenCount, user, doSeeAllNotifications } = props;
const { unseenCount, doSeeAllNotifications } = props;
const { push } = useHistory();
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS;
function handleMenuClick() {
if (unseenCount > 0) doSeeAllNotifications();

View file

@ -1,18 +1,9 @@
import { connect } from 'react-redux';
import { doOpenModal } from 'redux/actions/app';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { selectUserEmail, selectUserVerifiedEmail } from 'redux/selectors/user';
import * as MODALS from 'constants/modal_types';
import HeaderProfileMenuButton from './view';
const select = (state) => ({
activeChannelClaim: selectActiveChannelClaim(state),
email: selectUserEmail(state),
authenticated: selectUserVerifiedEmail(state),
});
const perform = (dispatch) => ({
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
});
export default connect(select, perform)(HeaderProfileMenuButton);
export default connect(select)(HeaderProfileMenuButton);

View file

@ -1,6 +1,6 @@
// @flow
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import { Menu, MenuList, MenuButton } from '@reach/menu-button';
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import ChannelThumbnail from 'component/channelThumbnail';
@ -11,13 +11,10 @@ import React from 'react';
type HeaderMenuButtonProps = {
activeChannelClaim: ?ChannelClaim,
email: ?string,
authenticated: boolean,
openSignOutModal: () => void,
};
export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
const { activeChannelClaim, email, openSignOutModal, authenticated } = props;
const { activeChannelClaim } = props;
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
@ -43,8 +40,8 @@ export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
<HeaderMenuLink page={PAGES.UPLOADS} icon={ICONS.PUBLISH} name={__('Uploads')} />
<HeaderMenuLink page={PAGES.CHANNELS} icon={ICONS.CHANNEL} name={__('Channels')} />
<HeaderMenuLink page={PAGES.CREATOR_DASHBOARD} icon={ICONS.ANALYTICS} name={__('Creator Analytics')} />
{authenticated ? (
{/* No sync button for now
{authenticated ? (
<MenuItem onSelect={openSignOutModal}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
@ -53,8 +50,9 @@ export default function HeaderProfileMenuButton(props: HeaderMenuButtonProps) {
<span className="menu__link-help">{email}</span>
</MenuItem>
) : (
<HeaderMenuLink page={PAGES.AUTH_SIGNIN} icon={ICONS.SIGN_IN} name={__('Cloud Connect')} />
<HeaderMenuLink page={PAGES.AUTH_SIGNIN} icon={ICONS.SIGN_IN} name={__('Maybe Sync')} />
)}
*/}
</MenuList>
</Menu>
</div>

View file

@ -1,11 +1,9 @@
import { connect } from 'react-redux';
import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
import { selectUser } from 'redux/selectors/user';
import NotificationHeaderButton from './view';
const select = (state) => ({
unseenCount: selectUnseenNotificationCount(state),
user: selectUser(state),
});
export default connect(select)(NotificationHeaderButton);

View file

@ -6,12 +6,11 @@ import { ENABLE_UI_NOTIFICATIONS } from 'config';
type Props = {
unseenCount: number,
inline: boolean,
user: ?User,
};
export default function NotificationHeaderButton(props: Props) {
const { unseenCount, inline = false, user } = props;
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
const { unseenCount, inline = false } = props;
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS;
if (unseenCount === 0 || !notificationsEnabled) {
return null;

View file

@ -1,9 +1,3 @@
import { connect } from 'react-redux';
import { selectUser } from 'redux/selectors/user';
import NudgeFloating from './view';
const select = state => ({
user: selectUser(state),
});
export default connect(select)(NudgeFloating);
export default NudgeFloating;

View file

@ -5,22 +5,20 @@ import usePersistedState from 'effects/use-persisted-state';
import Button from 'component/button';
type Props = {
user: ?User,
name: string,
text: string,
};
export default function NudgeFloating(props: Props) {
const { user, name, text } = props;
const { name, text } = props;
const [showNudge, setShowNudge] = React.useState(false);
const [nudgeAcknowledged, setNudgeAcknowledged] = usePersistedState(name, false);
const emailVerified = user && user.has_verified_email;
React.useEffect(() => {
if (!emailVerified && !nudgeAcknowledged) {
if (!nudgeAcknowledged) {
setShowNudge(true);
}
}, [emailVerified, nudgeAcknowledged]);
}, [nudgeAcknowledged]);
return (
showNudge && (

View file

@ -1,23 +1,11 @@
import { DOMAIN } from 'config';
import { connect } from 'react-redux';
import { doSetDaemonSetting } from 'redux/actions/settings';
import { doSignOut } from 'redux/actions/app';
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { doAuthenticate } from 'redux/actions/user';
import { version as appVersion } from 'package.json';
import PrivacyAgreement from './view';
const select = (state) => ({
authenticated: selectUserVerifiedEmail(state),
});
const perform = (dispatch) => ({
setShareDataInternal: (share) => dispatch(doSetDaemonSetting(DAEMON_SETTINGS.SHARE_USAGE_DATA, share)),
signOut: () => dispatch(doSignOut()),
authenticateIfSharingData: () =>
dispatch(doAuthenticate(appVersion, undefined, undefined, true, undefined, undefined, DOMAIN)),
});
export default connect(select, perform)(PrivacyAgreement);
export default connect(null, perform)(PrivacyAgreement);

View file

@ -1,7 +1,6 @@
// @flow
import React, { useState } from 'react';
import Button from 'component/button';
import I18nMessage from 'component/i18nMessage';
import { FormField } from 'component/common/form-components/form-field';
import { Form } from 'component/common/form-components/form';
import { withRouter } from 'react-router-dom';
@ -14,13 +13,11 @@ const NONE = 'none';
type Props = {
signOut: () => void,
setShareDataInternal: (boolean) => void,
authenticated: boolean,
authenticateIfSharingData: () => void,
handleNextPage: () => void,
};
function PrivacyAgreement(props: Props) {
const { setShareDataInternal, authenticated, signOut, authenticateIfSharingData, handleNextPage } = props;
const { setShareDataInternal, handleNextPage } = props;
const [share, setShare] = useState(undefined); // preload
function handleSubmit() {
@ -30,10 +27,6 @@ function PrivacyAgreement(props: Props) {
setShareDataInternal(false);
}
if (share === LIMITED) {
authenticateIfSharingData();
}
handleNextPage();
}
@ -63,7 +56,6 @@ function PrivacyAgreement(props: Props) {
onChange={(e) => setShare(LIMITED)}
/>
<FormField
disabled={authenticated}
name={'shareNot'}
type="radio"
checked={share === NONE}
@ -77,19 +69,6 @@ function PrivacyAgreement(props: Props) {
)}
onChange={(e) => setShare(NONE)}
/>
{authenticated && (
<div className="card--inline section--padded">
<p className="help--inline">
<I18nMessage
tokens={{
signout_button: <Button button="link" label={__('Sign Out')} onClick={signOut} />,
}}
>
You are signed in and sharing data with your cloud service provider. %signout_button%.
</I18nMessage>
</p>
</div>
)}
</fieldset>
<div className={'card__actions'}>
<Button button="primary" label={__(`Next`)} disabled={!share} type="submit" />

View file

@ -2,18 +2,13 @@ import { connect } from 'react-redux';
import { selectPublishFormValues } from 'redux/selectors/publish';
import { doUpdatePublishForm } from 'redux/actions/publish';
import PublishAdditionalOptions from './view';
import { selectUser, selectAccessToken } from 'redux/selectors/user';
import { doFetchAccessToken } from 'redux/actions/user';
const select = (state) => ({
...selectPublishFormValues(state),
accessToken: selectAccessToken(state),
user: selectUser(state),
});
const perform = (dispatch) => ({
updatePublishForm: (value) => dispatch(doUpdatePublishForm(value)),
fetchAccessToken: () => dispatch(doFetchAccessToken()),
});
export default connect(select, perform)(PublishAdditionalOptions);

View file

@ -10,14 +10,7 @@ import Card from 'component/common/card';
import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import { sortLanguageMap } from 'util/default-languages';
// @if TARGET='app'
// import ErrorText from 'component/common/error-text';
// import { LbryFirst } from 'lbry-redux';
// import { ipcRenderer } from 'electron';
// @endif
type Props = {
user: ?User,
language: ?string,
name: ?string,
licenseType: ?string,
@ -25,92 +18,16 @@ type Props = {
licenseUrl: ?string,
disabled: boolean,
updatePublishForm: ({}) => void,
useLBRYUploader: boolean,
needsYTAuth: boolean,
fetchAccessToken: () => void,
accessToken: string,
};
function PublishAdditionalOptions(props: Props) {
const {
language,
name,
licenseType,
otherLicenseDescription,
licenseUrl,
updatePublishForm,
// user,
// useLBRYUploader,
// needsYTAuth,
// accessToken,
// fetchAccessToken,
} = props;
const { language, name, licenseType, otherLicenseDescription, licenseUrl, updatePublishForm } = props;
const [hideSection, setHideSection] = usePersistedState('publish-advanced-options', true);
// const [hasLaunchedLbryFirst, setHasLaunchedLbryFirst] = React.useState(false);
// const [ytError, setYtError] = React.useState(false);
// const isLBRYFirstUser = user && user.lbry_first_approved;
// const showLbryFirstCheckbox = !IS_WEB && isLBRYFirstUser && hasLaunchedLbryFirst;
function toggleHideSection() {
setHideSection(!hideSection);
}
// @if TARGET='app'
// function signup() {
// updatePublishForm({ ytSignupPending: true });
// LbryFirst.ytSignup()
// .then(response => {
// updatePublishForm({ needsYTAuth: false, ytSignupPending: false });
// })
// .catch(error => {
// updatePublishForm({ ytSignupPending: false });
// setYtError(true);
// console.error(error); // eslint-disable-line
// });
// }
// function unlink() {
// setYtError(false);
// LbryFirst.remove()
// .then(response => {
// updatePublishForm({ needsYTAuth: true });
// })
// .catch(error => {
// setYtError(true);
// console.error(error); // eslint-disable-line
// });
// }
// React.useEffect(() => {
// if (!accessToken) {
// fetchAccessToken();
// }
// }, [accessToken, fetchAccessToken]);
// React.useEffect(() => {
// if (isLBRYFirstUser && !hasLaunchedLbryFirst) {
// ipcRenderer.send('launch-lbry-first');
// ipcRenderer.on('lbry-first-launched', () => {
// setHasLaunchedLbryFirst(true);
// });
// }
// }, [isLBRYFirstUser, hasLaunchedLbryFirst, setHasLaunchedLbryFirst]);
// React.useEffect(() => {
// if (useLBRYUploader && isLBRYFirstUser && hasLaunchedLbryFirst && accessToken) {
// LbryFirst.hasYTAuth(accessToken)
// .then(response => {
// updatePublishForm({ needsYTAuth: !response.HasAuth });
// })
// .catch(error => {
// setYtError(true);
// console.error(error); // eslint-disable-line
// });
// }
// }, [updatePublishForm, useLBRYUploader, isLBRYFirstUser, hasLaunchedLbryFirst, accessToken]);
// @endif
return (
<Card
className="card--enable-overflow"
@ -118,41 +35,6 @@ function PublishAdditionalOptions(props: Props) {
<React.Fragment>
{!hideSection && (
<div className={classnames({ 'card--disabled': !name })}>
{/* @if TARGET='app' */}
{/* {showLbryFirstCheckbox && (
<div className="section">
<>
<FormField
checked={useLBRYUploader}
type="checkbox"
name="use_lbry_uploader_checkbox"
onChange={event => updatePublishForm({ useLBRYUploader: !useLBRYUploader })}
label={
<React.Fragment>
{__('Automagically upload to your youtube channel.')}{' '}
<Button button="link" href="https://lbry.com/faq/lbry-uploader" label={__('Learn More')} />
</React.Fragment>
}
/>
{useLBRYUploader && (
<div className="section__actions">
{needsYTAuth ? (
<Button
button="primary"
onClick={signup}
label={__('Log In With YouTube')}
disabled={false}
/>
) : (
<Button button="alt" onClick={unlink} label={__('Unlink YouTube Channel')} disabled={false} />
)}
{ytError && <ErrorText>{__('There was an error with LBRY first publishing.')}</ErrorText>}
</div>
)}
</>
</div>
)} */}
{/* @endif */}
<div className="section">
<PublishReleaseDate />

View file

@ -17,12 +17,6 @@ import {
} from 'redux/selectors/publish';
import * as RENDER_MODES from 'constants/file_render_modes';
import * as SETTINGS from 'constants/settings';
import { doClaimInitialRewards } from 'redux/actions/rewards';
import {
selectUnclaimedRewardValue,
selectIsClaimingInitialRewards,
selectHasClaimedInitialRewards,
} from 'redux/selectors/rewards';
import {
selectModal,
selectActiveChannelClaim,
@ -31,7 +25,6 @@ import {
} from 'redux/selectors/app';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { makeSelectFileRenderModeForUri } from 'redux/selectors/content';
import { selectUser } from 'redux/selectors/user';
import PublishForm from './view';
const select = (state) => {
@ -41,7 +34,6 @@ const select = (state) => {
return {
...selectPublishFormValues(state),
user: selectUser(state),
// The winning claim for a short lbry uri
amountNeededForTakeover: selectTakeOverAmount(state),
isPostClaim,
@ -55,14 +47,11 @@ const select = (state) => {
remoteUrl: makeSelectPublishFormValue('remoteFileUrl')(state),
publishSuccess: makeSelectPublishFormValue('publishSuccess')(state),
isResolvingUri: selectIsResolvingPublishUris(state),
totalRewardValue: selectUnclaimedRewardValue(state),
modal: selectModal(state),
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
activeChannelClaim: selectActiveChannelClaim(state),
incognito: selectIncognito(state),
activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
isClaimingInitialRewards: selectIsClaimingInitialRewards(state),
hasClaimedInitialRewards: selectHasClaimedInitialRewards(state),
};
};
@ -74,7 +63,6 @@ const perform = (dispatch) => ({
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
checkAvailability: (name) => dispatch(doCheckPublishNameAvailability(name)),
claimInitialRewards: () => dispatch(doClaimInitialRewards()),
});
export default connect(select, perform)(PublishForm);

View file

@ -62,7 +62,6 @@ type Props = {
licenseType: string,
otherLicenseDescription: ?string,
licenseUrl: ?string,
useLBRYUploader: ?boolean,
publishing: boolean,
publishSuccess: boolean,
balance: number,
@ -76,19 +75,14 @@ type Props = {
// Add back type
updatePublishForm: (any) => void,
checkAvailability: (string) => void,
ytSignupPending: boolean,
modal: { id: string, modalProps: {} },
enablePublishPreview: boolean,
activeChannelClaim: ?ChannelClaim,
incognito: boolean,
user: ?User,
activeChannelStakedLevel: number,
isPostClaim: boolean,
permanentUrl: ?string,
remoteUrl: ?string,
isClaimingInitialRewards: boolean,
claimInitialRewards: () => void,
hasClaimedInitialRewards: boolean,
};
function PublishForm(props: Props) {
@ -116,7 +110,6 @@ function PublishForm(props: Props) {
publish,
disabled = false,
checkAvailability,
ytSignupPending,
modal,
enablePublishPreview,
activeChannelClaim,
@ -124,9 +117,6 @@ function PublishForm(props: Props) {
isPostClaim,
permanentUrl,
remoteUrl,
isClaimingInitialRewards,
claimInitialRewards,
hasClaimedInitialRewards,
} = props;
const inEditMode = Boolean(editingURI);
@ -224,12 +214,6 @@ function PublishForm(props: Props) {
const [previewing, setPreviewing] = React.useState(false);
useEffect(() => {
if (!hasClaimedInitialRewards) {
claimInitialRewards();
}
}, [hasClaimedInitialRewards, claimInitialRewards]);
useEffect(() => {
if (!modal) {
setTimeout(() => {
@ -240,9 +224,7 @@ function PublishForm(props: Props) {
let submitLabel;
if (isClaimingInitialRewards) {
submitLabel = __('Claiming credits...');
} else if (publishing) {
if (publishing) {
if (isStillEditing) {
submitLabel = __('Saving...');
} else {
@ -536,12 +518,7 @@ function PublishForm(props: Props) {
onClick={handlePublish}
label={submitLabel}
disabled={
isClaimingInitialRewards ||
formDisabled ||
!formValid ||
uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS ||
ytSignupPending ||
previewing
formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS || previewing
}
/>
<Button button="link" onClick={clearPublish} label={__('New --[clears Publish Form]--')} />

View file

@ -6,7 +6,6 @@ import Card from 'component/common/card';
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
import Button from 'component/button';
import classnames from 'classnames';
import RecSys from 'recsys';
const VIEW_ALL_RELATED = 'view_all_related';
const VIEW_MORE_FROM = 'view_more_from';
@ -18,48 +17,20 @@ type Props = {
isSearching: boolean,
doFetchRecommendedContent: (string) => void,
claim: ?StreamClaim,
claimId: string,
};
export default React.memo<Props>(function RecommendedContent(props: Props) {
const {
uri,
doFetchRecommendedContent,
recommendedContentUris,
nextRecommendedUri,
isSearching,
claim,
claimId,
} = props;
const { uri, doFetchRecommendedContent, recommendedContentUris, isSearching, claim } = props;
const [viewMode, setViewMode] = React.useState(VIEW_ALL_RELATED);
const signingChannel = claim && claim.signing_channel;
const channelName = signingChannel ? signingChannel.name : null;
const isMobile = useIsMobile();
const isMedium = useIsMediumScreen();
const { onRecsLoaded: onRecommendationsLoaded, onClickedRecommended: onRecommendationClicked } = RecSys;
React.useEffect(() => {
doFetchRecommendedContent(uri);
}, [uri, doFetchRecommendedContent]);
React.useEffect(() => {
// Right now we only want to record the recs if they actually saw them.
if (
recommendedContentUris &&
recommendedContentUris.length &&
nextRecommendedUri &&
viewMode === VIEW_ALL_RELATED
) {
onRecommendationsLoaded(claimId, recommendedContentUris);
}
}, [recommendedContentUris, onRecommendationsLoaded, claimId, nextRecommendedUri, viewMode]);
function handleRecommendationClicked(e, clickedClaim) {
if (claim) {
onRecommendationClicked(claim.claim_id, clickedClaim.claim_id);
}
}
return (
<Card
isBodyList
@ -96,7 +67,6 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
uris={recommendedContentUris}
hideMenu={isMobile}
empty={__('No related content found')}
onClick={handleRecommendationClicked}
/>
)}
{viewMode === VIEW_MORE_FROM && signingChannel && (

View file

@ -1,13 +1,10 @@
import { connect } from 'react-redux';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectHasNavigated, selectScrollStartingPosition, selectWelcomeVersion } from 'redux/selectors/app';
import { selectHomepageData } from 'redux/selectors/settings';
import Router from './view';
import { normalizeURI } from 'util/lbryURI';
import { selectTitleForUri } from 'redux/selectors/claims';
import { doSetHasNavigated } from 'redux/actions/app';
import { doUserSetReferrer } from 'redux/actions/user';
import { selectHasUnclaimedRefereeReward } from 'redux/selectors/rewards';
const select = (state) => {
const { pathname, hash } = state.router.location;
@ -30,17 +27,14 @@ const select = (state) => {
uri,
title: selectTitleForUri(state, uri),
currentScroll: selectScrollStartingPosition(state),
isAuthenticated: selectUserVerifiedEmail(state),
welcomeVersion: selectWelcomeVersion(state),
hasNavigated: selectHasNavigated(state),
hasUnclaimedRefereeReward: selectHasUnclaimedRefereeReward(state),
homepageData: selectHomepageData(state),
};
};
const perform = (dispatch) => ({
setHasNavigated: () => dispatch(doSetHasNavigated()),
setReferrer: (referrer) => dispatch(doUserSetReferrer(referrer)),
});
export default connect(select, perform)(Router);

View file

@ -5,7 +5,7 @@ import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
import * as PAGES from 'constants/pages';
import { PAGE_TITLE } from 'constants/pageTitles';
import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment';
import { parseURI, isURIValid } from 'util/lbryURI';
import { parseURI } from 'util/lbryURI';
import { WELCOME_VERSION } from 'config';
import { GetLinksData } from 'util/buildHomepage';
import { useIsLargeScreen } from 'effects/use-screensize';
@ -14,14 +14,9 @@ import HomePage from 'page/home';
import BackupPage from 'page/backup';
// Chunk: "secondary"
import SignInPage from 'page/signIn';
import SignInWalletPasswordPage from 'page/signInWalletPassword';
import SignUpPage from 'page/signUp';
import SignInVerifyPage from 'page/signInVerify';
// Chunk: "wallet/secondary"
import BuyPage from 'page/buy';
import ReceivePage from 'page/receive';
import SendPage from 'page/send';
import WalletPage from 'page/wallet';
@ -43,8 +38,6 @@ import ListBlockedPage from 'page/listBlocked';
import ListsPage from 'page/lists';
import PlaylistsPage from 'page/playlists';
import OwnComments from 'page/ownComments';
import PasswordResetPage from 'page/passwordReset';
import PasswordSetPage from 'page/passwordSet';
import PublishPage from 'page/publish';
import ReportContentPage from 'page/reportContent';
import ReportPage from 'page/report';
@ -70,7 +63,6 @@ if ('scrollRestoration' in history) {
type Props = {
currentScroll: number,
isAuthenticated: boolean,
location: { pathname: string, search: string, hash: string },
history: {
action: string,
@ -90,14 +82,12 @@ type Props = {
welcomeVersion: number,
hasNavigated: boolean,
setHasNavigated: () => void,
setReferrer: (?string) => void,
hasUnclaimedRefereeReward: boolean,
homepageData: any,
};
type PrivateRouteProps = Props & {
component: any,
isAuthenticated: boolean,
isAuthenticated?: boolean,
};
function PrivateRoute(props: PrivateRouteProps) {
@ -109,15 +99,12 @@ function AppRouter(props: Props) {
const {
currentScroll,
location: { pathname, search, hash },
isAuthenticated,
history,
uri,
title,
welcomeVersion,
hasNavigated,
setHasNavigated,
hasUnclaimedRefereeReward,
setReferrer,
homepageData,
} = props;
const { entries, listen, action: historyAction } = history;
@ -140,16 +127,6 @@ function AppRouter(props: Props) {
return unlisten;
}, [listen, hasNavigated, setHasNavigated]);
useEffect(() => {
if (!hasNavigated && hasUnclaimedRefereeReward && !isAuthenticated) {
const valid = isURIValid(uri);
if (valid) {
const { path } = parseURI(uri);
if (path !== 'undefined') setReferrer(path);
}
}
}, [hasNavigated, uri, hasUnclaimedRefereeReward, setReferrer, isAuthenticated]);
useEffect(() => {
const getDefaultTitle = (pathname: string) => {
const title = pathname.startsWith('/$/') ? PAGE_TITLE[pathname.substring(3)] : '';
@ -175,9 +152,7 @@ function AppRouter(props: Props) {
document.title = getDefaultTitle(pathname);
}
// @if TARGET='app'
entries[entryIndex].title = document.title;
// @endif
}, [pathname, entries, entryIndex, title, uri]);
useEffect(() => {
@ -227,19 +202,10 @@ function AppRouter(props: Props) {
))}
{/* Odysee signin */}
<Route path={`/$/${PAGES.AUTH_SIGNIN}`} exact component={SignInPage} />
<Route path={`/$/${PAGES.AUTH_PASSWORD_RESET}`} exact component={PasswordResetPage} />
<Route path={`/$/${PAGES.AUTH_PASSWORD_SET}`} exact component={PasswordSetPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={SignUpPage} />
<Route path={`/$/${PAGES.AUTH}/*`} exact component={SignUpPage} />
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
<Route path={`/$/${PAGES.WELCOME}`} exact component={Welcome} />
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
{/* @if TARGET='app' */}
<Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} />
{/* @endif */}
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
<Route path={`/$/${PAGES.TOP}`} exact component={TopPage} />
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
@ -270,7 +236,6 @@ function AppRouter(props: Props) {
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_CREATOR}`} component={SettingsCreatorPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.BUY}`} component={BuyPage} />
<PrivateRoute {...props} path={`/$/${PAGES.RECEIVE}`} component={ReceivePage} />
<PrivateRoute {...props} path={`/$/${PAGES.SEND}`} component={SendPage} />
<PrivateRoute {...props} path={`/$/${PAGES.NOTIFICATIONS}`} component={NotificationsPage} />

View file

@ -3,7 +3,6 @@ import SelectChannel from './view';
import { selectBalance } from 'redux/selectors/wallet';
import { selectMyChannelClaims, selectFetchingMyChannels } from 'redux/selectors/claims';
import { doFetchChannelListMine, doCreateChannel } from 'redux/actions/claims';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { doSetActiveChannel } from 'redux/actions/app';
@ -11,7 +10,6 @@ const select = (state) => ({
myChannelClaims: selectMyChannelClaims(state),
fetchingChannels: selectFetchingMyChannels(state),
balance: selectBalance(state),
emailVerified: selectUserVerifiedEmail(state),
activeChannelClaim: selectActiveChannelClaim(state),
});

View file

@ -2,14 +2,11 @@ import { connect } from 'react-redux';
import { selectHasChannels } from 'redux/selectors/claims';
import { selectWalletIsEncrypted } from 'redux/selectors/wallet';
import { doWalletStatus } from 'redux/actions/wallet';
import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
import SettingAccount from './view';
const select = (state) => ({
isAuthenticated: selectUserVerifiedEmail(state),
walletEncrypted: selectWalletIsEncrypted(state),
user: selectUser(state),
hasChannels: selectHasChannels(state),
});

View file

@ -6,27 +6,26 @@ 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';
// maybe bring this back
// import SyncToggle from 'component/syncToggle';
import { getPasswordFromCookie } from 'util/saved-passwords';
type Props = {
isAuthenticated: boolean,
walletEncrypted: boolean,
user: User,
hasChannels: boolean,
doWalletStatus: () => void,
};
export default function SettingAccount(props: Props) {
const { isAuthenticated, walletEncrypted, hasChannels, doWalletStatus } = props;
const [storedPassword, setStoredPassword] = React.useState(false);
const { hasChannels, doWalletStatus } = props;
// const [storedPassword, setStoredPassword] = React.useState(false);
// Determine if password is stored.
React.useEffect(() => {
doWalletStatus();
getPasswordFromCookie().then((p) => {
if (typeof p === 'string') {
setStoredPassword(true);
// get password
}
});
}, []); // eslint-disable-line react-hooks/exhaustive-deps
@ -42,18 +41,8 @@ export default function SettingAccount(props: Props) {
isBodyList
body={
<>
{isAuthenticated && (
<SettingsRow title={__('Password')}>
<Button
button="inverse"
label={__('Manage')}
icon={ICONS.ARROW_RIGHT}
navigate={`/$/${PAGES.SETTINGS_UPDATE_PWD}`}
/>
</SettingsRow>
)}
<SyncToggle disabled={walletEncrypted && !storedPassword && storedPassword !== ''} />
{/* This will probably start the new sync flow when checked (-> openModal(SYNC_ENABLE) ) */}
{/* <SyncToggle disabled={true} /> */}
{hasChannels && (
<SettingsRow title={__('Comments')} subtitle={__('View your past comments.')}>

View file

@ -1,17 +0,0 @@
import { connect } from 'react-redux';
import { selectUser, selectPasswordSetSuccess, selectPasswordSetError } from 'redux/selectors/user';
import { doUserPasswordSet, doClearPasswordEntry } from 'redux/actions/user';
import { doToast } from 'redux/actions/notifications';
import UserSignIn from './view';
const select = (state) => ({
user: selectUser(state),
passwordSetSuccess: selectPasswordSetSuccess(state),
passwordSetError: selectPasswordSetError(state),
});
export default connect(select, {
doUserPasswordSet,
doToast,
doClearPasswordEntry,
})(UserSignIn);

View file

@ -1,81 +0,0 @@
// @flow
import React, { useState } from 'react';
import { useHistory } from 'react-router';
import { FormField, Form } from 'component/common/form';
import Button from 'component/button';
import ErrorText from 'component/common/error-text';
import SettingsRow from 'component/settingsRow';
import * as PAGES from 'constants/pages';
type Props = {
user: ?User,
doToast: ({ message: string }) => void,
doUserPasswordSet: (string, ?string) => void,
doClearPasswordEntry: () => void,
passwordSetSuccess: boolean,
passwordSetError: ?string,
};
export default function SettingAccountPassword(props: Props) {
const { user, doToast, doUserPasswordSet, passwordSetSuccess, passwordSetError, doClearPasswordEntry } = props;
const [oldPassword, setOldPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const hasPassword = user && user.password_set;
const { goBack } = useHistory();
const title = hasPassword ? __('Update Your Password') : __('Add A Password');
const subtitle = hasPassword ? '' : __('You do not currently have a password set.');
function handleSubmit() {
doUserPasswordSet(newPassword, oldPassword);
}
React.useEffect(() => {
if (passwordSetSuccess) {
goBack();
doToast({
message: __('Password updated successfully.'),
});
doClearPasswordEntry();
setOldPassword('');
setNewPassword('');
}
}, [passwordSetSuccess, setOldPassword, setNewPassword, doClearPasswordEntry, doToast, goBack]);
return (
<SettingsRow title={title} subtitle={subtitle} multirow>
<Form onSubmit={handleSubmit} className="section">
{hasPassword && (
<FormField
type="password"
name="setting_set_old_password"
label={__('Old Password')}
value={oldPassword}
onChange={(e) => setOldPassword(e.target.value)}
/>
)}
<FormField
type="password"
name="setting_set_new_password"
label={__('New Password')}
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
<div className="section__actions">
<Button button="primary" type="submit" label={__('Set Password')} disabled={!newPassword} />
{hasPassword ? (
<Button button="link" label={__('Forgot Password?')} navigate={`/$/${PAGES.AUTH_PASSWORD_RESET}`} />
) : (
<Button button="link" label={__('Cancel')} onClick={() => goBack()} />
)}
</div>
</Form>
{passwordSetError && (
<div className="section">
<ErrorText>{passwordSetError}</ErrorText>
</div>
)}
</SettingsRow>
);
}

View file

@ -4,12 +4,10 @@ import * as SETTINGS from 'constants/settings';
import { doSetPlayingUri, clearContentCache } from 'redux/actions/content';
import { doSetClientSetting } from 'redux/actions/settings';
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import SettingContent from './view';
const select = (state) => ({
isAuthenticated: selectUserVerifiedEmail(state),
floatingPlayer: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),
autoplayMedia: makeSelectClientSetting(SETTINGS.AUTOPLAY_MEDIA)(state),
autoplayNext: makeSelectClientSetting(SETTINGS.AUTOPLAY_NEXT)(state),

View file

@ -3,7 +3,6 @@ import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import React from 'react';
import * as SETTINGS from 'constants/settings';
import { Lbryio } from 'lbryinc';
import { SETTINGS_GRP } from 'constants/settings';
import Button from 'component/button';
import Card from 'component/common/card';
@ -18,7 +17,6 @@ type Price = {
type Props = {
// --- select ---
isAuthenticated: boolean,
floatingPlayer: boolean,
autoplayMedia: boolean,
autoplayNext: boolean,
@ -37,7 +35,6 @@ type Props = {
export default function SettingContent(props: Props) {
const {
isAuthenticated,
floatingPlayer,
autoplayMedia,
autoplayNext,
@ -110,10 +107,6 @@ export default function SettingContent(props: Props) {
type="checkbox"
name="hide_reposts"
onChange={(e) => {
if (isAuthenticated) {
let param = e.target.checked ? { add: 'noreposts' } : { remove: 'noreposts' };
Lbryio.call('user_tag', 'edit', param);
}
setClientSetting(SETTINGS.HIDE_REPOSTS, !hideReposts);
}}
checked={hideReposts}
@ -172,7 +165,7 @@ export default function SettingContent(props: Props) {
</SettingsRow>
{myChannelUrls && myChannelUrls.length > 0 && (
<SettingsRow title={__('Creator settings')}>
<SettingsRow title={__('Creator Comment settings')}>
<Button
button="inverse"
label={__('Manage')}

View file

@ -11,7 +11,6 @@ import {
import { doSetDaemonSetting, doClearDaemonSetting, doFindFFmpeg } from 'redux/actions/settings';
import { selectAllowAnalytics } from 'redux/selectors/app';
import { selectDaemonSettings, selectFfmpegStatus, selectFindingFFmpeg } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user'; // here
import SettingSystem from './view';
@ -20,7 +19,6 @@ const select = (state) => ({
ffmpegStatus: selectFfmpegStatus(state),
findingFFmpeg: selectFindingFFmpeg(state),
walletEncrypted: selectWalletIsEncrypted(state),
isAuthenticated: selectUserVerifiedEmail(state),
allowAnalytics: selectAllowAnalytics(state),
});

View file

@ -44,7 +44,6 @@ type Props = {
ffmpegStatus: { available: boolean, which: string },
findingFFmpeg: boolean,
walletEncrypted: boolean,
isAuthenticated: boolean,
allowAnalytics: boolean,
// --- perform ---
setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
@ -64,7 +63,6 @@ export default function SettingSystem(props: Props) {
ffmpegStatus,
findingFFmpeg,
walletEncrypted,
isAuthenticated,
allowAnalytics,
setDaemonSetting,
clearDaemonSetting,
@ -168,12 +166,7 @@ export default function SettingSystem(props: Props) {
onChange={() => setDaemonSetting('share_usage_data', !daemonSettings.share_usage_data)}
checked={daemonSettings.share_usage_data}
label={<React.Fragment>{__('Allow the app to share data to LBRY.inc')}</React.Fragment>}
helper={
isAuthenticated
? __('Internal sharing is required while signed in.')
: __('Internal sharing is required to participate in rewards programs.')
}
disabled={isAuthenticated && daemonSettings.share_usage_data}
helper={__('Internal sharing is required to participate in rewards programs.')}
/>
<FormField
type="checkbox"

View file

@ -2,7 +2,6 @@ import { connect } from 'react-redux';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { doClearPurchasedUriSuccess } from 'redux/actions/file';
import { selectFollowedTags } from 'redux/selectors/tags';
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
import { selectHomepageData } from 'redux/selectors/settings';
import { doSignOut } from 'redux/actions/app';
import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
@ -13,10 +12,8 @@ import SideNavigation from './view';
const select = (state) => ({
subscriptions: selectSubscriptions(state),
followedTags: selectFollowedTags(state),
email: selectUserVerifiedEmail(state),
purchaseSuccess: selectPurchaseUriSuccess(state),
unseenCount: selectUnseenNotificationCount(state),
user: selectUser(state),
homepageData: selectHomepageData(state),
});

View file

@ -6,13 +6,11 @@ import * as KEYCODES from 'constants/keycodes';
import React from 'react';
import Button from 'component/button';
import classnames from 'classnames';
import NotificationBubble from 'component/notificationBubble';
import DebouncedInput from 'component/common/debounced-input';
import ChannelThumbnail from 'component/channelThumbnail';
import { useIsMobile, isTouch } from 'effects/use-screensize';
import { IS_MAC } from 'component/app/view';
import { useHistory } from 'react-router';
import { ENABLE_UI_NOTIFICATIONS } from 'config';
const FOLLOWED_ITEM_INITIAL_LIMIT = 10;
const touch = isTouch();
@ -32,7 +30,6 @@ type SideNavLink = {
type Props = {
subscriptions: Array<Subscription>,
followedTags: Array<Tag>,
email: ?string,
uploadCount: number,
doSignOut: () => void,
sidebarOpen: boolean,
@ -42,7 +39,6 @@ type Props = {
unseenCount: number,
purchaseSuccess: boolean,
doClearPurchasedUriSuccess: () => void,
user: ?User,
homepageData: any,
activeChannelStakedLevel: number,
};
@ -51,7 +47,6 @@ function SideNavigation(props: Props) {
const {
subscriptions,
doSignOut,
email,
purchaseSuccess,
doClearPurchasedUriSuccess,
sidebarOpen,
@ -59,7 +54,6 @@ function SideNavigation(props: Props) {
isMediumScreen,
isOnFilePage,
unseenCount,
user,
followedTags,
} = props;
@ -99,13 +93,6 @@ function SideNavigation(props: Props) {
icon: ICONS.PURCHASED,
};
const NOTIFICATIONS = {
title: 'Notifications',
link: `/$/${PAGES.NOTIFICATIONS}`,
icon: ICONS.NOTIFICATION,
extra: <NotificationBubble inline />,
};
const PLAYLISTS = {
title: 'Lists',
link: `/$/${PAGES.LISTS}`,
@ -181,8 +168,6 @@ function SideNavigation(props: Props) {
},
];
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
const [pulseLibrary, setPulseLibrary] = React.useState(false);
const [expandSubscriptions, setExpandSubscriptions] = React.useState(false);
const [expandTags, setExpandTags] = React.useState(false);
@ -241,10 +226,6 @@ function SideNavigation(props: Props) {
const { hideForUnauth, route, link, ...passedProps } = props;
const { title, icon, extra } = passedProps;
if (hideForUnauth && !email) {
return null;
}
return (
<li key={route || link || title}>
<Button
@ -388,15 +369,11 @@ function SideNavigation(props: Props) {
'navigation--push': showPushMenu,
'navigation-file-page-and-mobile': hideMenuFromView,
'navigation-touch': touch,
// @if TARGET='app'
'navigation--mac': IS_MAC,
// @endif
})}
>
{(!canDisposeMenu || sidebarOpen) && (
<div className="navigation-inner-container">
<ul className="navigation-links--absolute mobile-only">{notificationsEnabled && getLink(NOTIFICATIONS)}</ul>
<ul
className={classnames('navigation-links', {
'navigation-links--micro': showMicroMenu,
@ -412,7 +389,7 @@ function SideNavigation(props: Props) {
{getLink(PLAYLISTS)}
</ul>
<ul className="navigation-links--absolute mobile-only">
{email && MOBILE_LINKS.map((linkProps) => getLink(linkProps))}
{MOBILE_LINKS.map((linkProps) => getLink(linkProps))}
</ul>
{getSubscriptionSection()}

View file

@ -63,8 +63,6 @@ function SocialShare(props: Props) {
const shareUrl: string = generateShareUrl(
shareDomain,
lbryUrl,
null,
null,
includeStartTime,
startTimeSeconds,
includedCollectionId

View file

@ -6,7 +6,6 @@ import {
makeSelectNotificationsDisabled,
} from 'redux/selectors/subscriptions';
import { makeSelectPermanentUrlForUri } from 'redux/selectors/claims';
import { selectUser } from 'redux/selectors/user';
import { doToast } from 'redux/actions/notifications';
import SubscribeButton from './view';
@ -15,7 +14,6 @@ const select = (state, props) => ({
firstRunCompleted: selectFirstRunCompleted(state),
permanentUrl: makeSelectPermanentUrlForUri(props.uri)(state),
notificationsDisabled: makeSelectNotificationsDisabled(props.uri)(state),
user: selectUser(state),
});
export default connect(select, {

View file

@ -21,7 +21,6 @@ type Props = {
doToast: ({ message: string }) => void,
shrinkOnMobile: boolean,
notificationsDisabled: boolean,
user: ?User,
uri: string,
};
@ -34,7 +33,6 @@ export default function SubscribeButton(props: Props) {
doToast,
shrinkOnMobile = false,
notificationsDisabled,
user,
uri,
} = props;
@ -42,7 +40,7 @@ export default function SubscribeButton(props: Props) {
const isMobile = useIsMobile();
let isHovering = useHover(buttonRef);
isHovering = isMobile ? true : isHovering;
const uiNotificationsEnabled = (user && user.experimental_ui) || ENABLE_UI_NOTIFICATIONS;
const uiNotificationsEnabled = ENABLE_UI_NOTIFICATIONS;
const { channelName: rawChannelName } = parseURI(uri);

View file

@ -1,6 +1,5 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import {
selectGetSyncErrorMessage,
selectHasSyncedWallet,
@ -17,7 +16,6 @@ const select = (state) => ({
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
hasSyncedWallet: selectHasSyncedWallet(state),
hasSyncChanged: selectHashChanged(state),
verifiedEmail: selectUserVerifiedEmail(state),
getSyncError: selectGetSyncErrorMessage(state),
getSyncPending: selectGetSyncIsPending(state),
});

View file

@ -1,18 +1,17 @@
import { connect } from 'react-redux';
import { selectGetSyncIsPending, selectSyncApplyPasswordError } from 'redux/selectors/sync';
import { doGetSyncDesktop } from 'redux/actions/sync';
import { selectUserEmail } from 'redux/selectors/user';
import { doSetClientSetting } from 'redux/actions/settings';
import { doSignOut, doHandleSyncComplete } from 'redux/actions/app';
import SyncPassword from './view';
const select = state => ({
const select = (state) => ({
getSyncIsPending: selectGetSyncIsPending(state),
email: selectUserEmail(state),
passwordError: selectSyncApplyPasswordError(state),
// bring email in from new sync system
});
const perform = dispatch => ({
const perform = (dispatch) => ({
getSync: (cb, password) => dispatch(doGetSyncDesktop(cb, password)),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
handleSyncComplete: (error, hasDataChanged) => dispatch(doHandleSyncComplete(error, hasDataChanged)),

View file

@ -12,14 +12,14 @@ import { SITE_HELP_EMAIL } from 'config';
type Props = {
getSync: ((any, boolean) => void, ?string) => void,
getSyncIsPending: boolean,
email: string,
passwordError: boolean,
signOut: () => void,
handleSyncComplete: (any, boolean) => void,
email: string,
};
function SyncPassword(props: Props) {
const { getSync, getSyncIsPending, email, signOut, passwordError, handleSyncComplete } = props;
const { getSync, getSyncIsPending, signOut, passwordError, handleSyncComplete, email = 'dummy' } = props;
const {
push,
location: { search },

View file

@ -1,6 +1,5 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetWalletSyncPreference } from 'redux/actions/settings';
@ -9,7 +8,6 @@ import SyncToggle from './view';
const select = (state) => ({
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
verifiedEmail: selectUserVerifiedEmail(state),
getSyncError: selectGetSyncErrorMessage(state),
});

View file

@ -1,7 +1,6 @@
// @flow
import * as MODALS from 'constants/modal_types';
import React from 'react';
import Button from 'component/button';
import SettingsRow from 'component/settingsRow';
import { withRouter } from 'react-router';
import { FormField } from 'component/common/form';
@ -9,7 +8,6 @@ import { FormField } from 'component/common/form';
type Props = {
setSyncEnabled: (boolean) => void,
syncEnabled: boolean,
verifiedEmail: ?string,
history: { push: (string) => void },
location: UrlLocation,
getSyncError: ?string,
@ -18,32 +16,24 @@ type Props = {
};
function SyncToggle(props: Props) {
const { verifiedEmail, openModal, syncEnabled, disabled } = props;
// Redesign for new sync system
const { openModal, syncEnabled, disabled } = props;
return (
<SettingsRow
title={__('Sync')}
subtitle={disabled || !verifiedEmail ? '' : __('Sync your balance and preferences across devices.')}
>
<SettingsRow title={__('Sync')} subtitle={disabled ? '' : __('Sync your balance and preferences across devices.')}>
<FormField
type="checkbox"
name="sync_toggle"
label={disabled || !verifiedEmail ? __('Sync your balance and preferences across devices.') : undefined}
checked={syncEnabled && verifiedEmail}
label={disabled ? __('Sync your balance and preferences across devices.') : undefined}
checked={syncEnabled}
onChange={() => openModal(MODALS.SYNC_ENABLE, { mode: syncEnabled ? 'disable' : 'enable' })}
disabled={disabled || !verifiedEmail}
disabled={disabled}
helper={
disabled
? __("To enable Sync, close LBRY completely and check 'Remember Password' during wallet unlock.")
: null
}
/>
{!verifiedEmail && (
<div>
<p className="help">{__('An email address is required to sync your account.')}</p>
<Button button="primary" label={__('Add Email')} />
</div>
)}
</SettingsRow>
);
}

View file

@ -1,13 +1,11 @@
import { connect } from 'react-redux';
import { selectUnfollowedTags, selectFollowedTags } from 'redux/selectors/tags';
import { doToggleTagFollowDesktop, doAddTag, doDeleteTag } from 'redux/actions/tags';
import { selectUser } from 'redux/selectors/user';
import DiscoveryFirstRun from './view';
const select = (state, props) => ({
unfollowedTags: props.unfollowedTags || selectUnfollowedTags(state),
followedTags: props.followedTags || selectFollowedTags(state),
user: selectUser(state),
});
export default connect(select, {

View file

@ -26,7 +26,6 @@ type Props = {
disabled?: boolean,
limitSelect?: number,
limitShow?: number,
user: User,
disableControlTags?: boolean,
};
@ -60,7 +59,6 @@ export default function TagsSearch(props: Props) {
disabled,
limitSelect = TAG_FOLLOW_MAX,
limitShow = 5,
user,
disableControlTags,
} = props;
const [newTag, setNewTag] = useState('');
@ -71,7 +69,7 @@ export default function TagsSearch(props: Props) {
// Make sure there are no duplicates, then trim
// suggestedTags = (followedTags - tagsPassedIn) + unfollowedTags
const experimentalFeature = user && user.experimental_ui;
const experimentalFeature = false;
const followedTagsSet = new Set(followedTags.map((tag) => tag.name));
const selectedTagsSet = new Set(tagsPassedIn.map((tag) => tag.name));
const unfollowedTagsSet = new Set(unfollowedTags.map((tag) => tag.name));
@ -88,7 +86,6 @@ export default function TagsSearch(props: Props) {
}
});
// const countWithoutLbryFirst = selectedTagsSet.has('lbry-first') ? selectedTagsSet.size - 1 : selectedTagsSet.size;
const maxed = Boolean(limitSelect && countWithoutSpecialTags >= limitSelect);
const suggestedTags = Array.from(suggestedTagsSet).filter(doesTagMatch).slice(0, limitShow);

View file

@ -1,11 +1,9 @@
import { connect } from 'react-redux';
import { selectClaimedRewardsByTransactionId } from 'redux/selectors/rewards';
import { doOpenModal } from 'redux/actions/app';
import { selectIsFetchingTxos } from 'redux/selectors/wallet';
import TransactionListTable from './view';
const select = (state) => ({
rewards: selectClaimedRewardsByTransactionId(state),
loading: selectIsFetchingTxos(state),
});

View file

@ -9,12 +9,11 @@ type Props = {
emptyMessage: ?string,
loading: boolean,
openModal: (id: string, { tx: Txo, cb: (string) => void }) => void,
rewards: {},
txos: Array<Txo>,
};
function TransactionListTable(props: Props) {
const { emptyMessage, rewards, loading, txos } = props;
const { emptyMessage, loading, txos } = props;
const REVOCABLE_TYPES = ['channel', 'stream', 'repost', 'support', 'claim', 'collection'];
function revokeClaim(tx: any, cb: (string) => void) {
props.openModal(MODALS.CONFIRM_CLAIM_REVOKE, { tx, cb });
@ -48,7 +47,6 @@ function TransactionListTable(props: Props) {
<TxoListItem
key={`${t.txid}:${t.nout}-${i}`}
txo={t}
reward={rewards && rewards[t.txid]}
isRevokeable={t.is_my_output && !t.is_spent && REVOCABLE_TYPES.includes(t.type)}
revokeClaim={revokeClaim}
/>

View file

@ -1,18 +0,0 @@
import { connect } from 'react-redux';
import { doUserResendVerificationEmail, doUserCheckEmailVerified, doFetchAccessToken } from 'redux/actions/user';
import { selectEmailToVerify, selectUser, selectAccessToken } from 'redux/selectors/user';
import UserEmailVerify from './view';
const select = state => ({
email: selectEmailToVerify(state),
user: selectUser(state),
accessToken: selectAccessToken(state),
});
const perform = dispatch => ({
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
checkEmailVerified: () => dispatch(doUserCheckEmailVerified()),
fetchAccessToken: () => dispatch(doFetchAccessToken()),
});
export default connect(select, perform)(UserEmailVerify);

View file

@ -1,73 +0,0 @@
// @flow
import * as PAGES from 'constants/pages';
import type { Node } from 'react';
import React, { useEffect } from 'react';
import Button from 'component/button';
import { FormField } from 'component/common/form';
import UserSignOutButton from 'component/userSignOutButton';
import Card from 'component/common/card';
type Props = {
cancelButton: Node,
email: string,
resendVerificationEmail: (string) => void,
checkEmailVerified: () => void,
user: {
has_verified_email: boolean,
},
fetchAccessToken: () => void,
accessToken: string,
};
function UserEmail(props: Props) {
const { email, user, accessToken, fetchAccessToken } = props;
let isVerified = false;
if (user) {
isVerified = user.has_verified_email;
}
useEffect(() => {
if (!accessToken) {
fetchAccessToken();
}
}, [accessToken, fetchAccessToken]);
return (
<Card
title={__('Cloud Account')}
subtitle={
isVerified
? undefined
: __(
'Connecting with a cloud account will allow you to earn rewards, receive content and security updates, and optionally backup your data.'
)
}
actions={
isVerified ? (
<FormField
type="text"
className="form-field--copyable"
readOnly
label={
<React.Fragment>
{__('Your email')}{' '}
<Button
button="link"
label={__('Update mailing preferences')}
href={`http://lbry.io/list/edit/${accessToken}`}
/>
</React.Fragment>
}
inputButton={<UserSignOutButton button="secondary" />}
value={email || ''}
/>
) : (
<Button button="primary" label={__('Log In')} navigate={`/$/${PAGES.AUTH}`} />
)
}
/>
);
}
export default UserEmail;

View file

@ -1,11 +1,5 @@
import { connect } from 'react-redux';
import { doClearEmailEntry, doUserSignUp } from 'redux/actions/user';
import {
selectEmailNewIsPending,
selectEmailNewErrorMessage,
selectEmailAlreadyExists,
selectUser,
} from 'redux/selectors/user';
// new sync stuff
import * as SETTINGS from 'constants/settings';
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
import { doSetWalletSyncPreference, doSetDaemonSetting } from 'redux/actions/settings';
@ -13,20 +7,14 @@ import { selectDaemonSettings, makeSelectClientSetting } from 'redux/selectors/s
import UserEmailNew from './view';
const select = (state) => ({
isPending: selectEmailNewIsPending(state),
errorMessage: selectEmailNewErrorMessage(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
daemonSettings: selectDaemonSettings(state),
emailExists: selectEmailAlreadyExists(state),
user: selectUser(state),
});
const perform = (dispatch) => ({
setSync: (value) => dispatch(doSetWalletSyncPreference(value)),
setShareDiagnosticData: (shouldShareData) =>
dispatch(doSetDaemonSetting(DAEMON_SETTINGS.SHARE_USAGE_DATA, shouldShareData)),
doSignUp: (email, password) => dispatch(doUserSignUp(email, password)),
clearEmailEntry: () => dispatch(doClearEmailEntry()),
});
export default connect(select, perform)(UserEmailNew);

View file

@ -1,102 +1,55 @@
// @flow
import * as PAGES from 'constants/pages';
import { DOMAIN } from 'config';
import React, { useState } from 'react';
/*
Saving this component for sign in/up
*/
import React from 'react';
import { FormField, Form } from 'component/common/form';
import Button from 'component/button';
import analytics from 'analytics';
import { EMAIL_REGEX } from 'constants/email';
import I18nMessage from 'component/i18nMessage';
import { useHistory } from 'react-router-dom';
import Card from 'component/common/card';
import ErrorText from 'component/common/error-text';
import Nag from 'component/common/nag';
import classnames from 'classnames';
type Props = {
errorMessage: ?string,
emailExists: boolean,
isPending: boolean,
// new sync stuff
syncEnabled: boolean,
setSync: (boolean) => void,
balance: number,
daemonSettings: { share_usage_data: boolean },
setShareDiagnosticData: (boolean) => void,
doSignUp: (string, ?string) => Promise<any>,
clearEmailEntry: () => void,
interestedInYoutubSync: boolean,
doToggleInterestedInYoutubeSync: () => void,
};
const SIGN_UP_MODE = 'signUp';
const SIGN_IN_MODE = 'signIn';
function UserEmailNew(props: Props) {
const {
errorMessage,
isPending,
doSignUp,
setSync,
daemonSettings,
setShareDiagnosticData,
clearEmailEntry,
emailExists,
} = props;
const { share_usage_data: shareUsageData } = daemonSettings;
const { push, location } = useHistory();
const urlParams = new URLSearchParams(location.search);
const emailFromUrl = urlParams.get('email');
const defaultEmail = emailFromUrl ? decodeURIComponent(emailFromUrl) : '';
const [email, setEmail] = useState(defaultEmail);
const [password, setPassword] = useState('');
const [localShareUsageData, setLocalShareUsageData] = React.useState(false);
const [formSyncEnabled, setFormSyncEnabled] = useState(true);
const valid = email.match(EMAIL_REGEX);
const [email, setEmail] = React.useState();
const [password, setPassword] = React.useState();
// const [server, setServer] = React.useState();
// const [errormessage, setErrorMessage] = React.useState();
const [mode, setMode] = React.useState(SIGN_UP_MODE);
function handleUsageDataChange() {
setLocalShareUsageData(!localShareUsageData);
}
function handleSubmit() {
// @if TARGET='app'
setSync(formSyncEnabled);
setShareDiagnosticData(true);
// @endif
doSignUp(email, password === '' ? undefined : password).then(() => {
analytics.emailProvidedEvent();
});
}
function handleChangeToSignIn(additionalParams) {
clearEmailEntry();
let url = `/$/${PAGES.AUTH_SIGNIN}`;
const urlParams = new URLSearchParams(location.search);
urlParams.delete('email');
if (email) {
urlParams.set('email', encodeURIComponent(email));
}
urlParams.delete('email_exists');
if (emailExists) {
urlParams.set('email_exists', '1');
}
push(`${url}?${urlParams.toString()}`);
}
React.useEffect(() => {
if (emailExists) {
handleChangeToSignIn();
}
}, [emailExists]);
// const shareUsageData = false;
const handleSubmit = () => {};
return (
<div className={classnames('main__sign-up')}>
<Card
title={__('Cloud Connect')}
subtitle={__('Connect your wallet to Odysee')}
actions={
<div className={classnames({ 'card--disabled': DOMAIN === 'lbry.tv' && IS_WEB })}>
<div>
<Form onSubmit={handleSubmit} className="section">
<FormField
autoFocus
placeholder={__('yourstruly@example.com')}
type="email"
name="sign_up_email"
label={__('Email')}
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<FormField
autoFocus
placeholder={__('yourstruly@example.com')}
@ -113,45 +66,13 @@ function UserEmailNew(props: Props) {
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<FormField
type="checkbox"
name="sync_checkbox"
label={
<React.Fragment>
{__('Backup your account and wallet data.')}{' '}
<Button button="link" href="https://lbry.com/faq/account-sync" label={__('Learn More')} />
</React.Fragment>
}
checked={formSyncEnabled}
onChange={() => setFormSyncEnabled(!formSyncEnabled)}
/>
{!shareUsageData && !IS_WEB && (
<FormField
type="checkbox"
name="share_data_checkbox"
checked={localShareUsageData}
onChange={handleUsageDataChange}
label={
<React.Fragment>
{__('Share usage data with LBRY inc.')}{' '}
<Button button="link" href="https://lbry.com/faq/privacy-and-data" label={__('Learn More')} />
{!localShareUsageData && <span className="error__text"> ({__('Required')})</span>}
</React.Fragment>
}
/>
)}
<div className="section__actions">
<Button button="primary" type="submit" label={__('Sign Up')} disabled={!email || !password} />
<Button
button="primary"
type="submit"
label={__('Sign Up')}
disabled={
!email || !password || !valid || (!IS_WEB && !localShareUsageData && !shareUsageData) || isPending
}
button="link"
onClick={setMode(mode === SIGN_UP_MODE ? SIGN_IN_MODE : SIGN_UP_MODE)}
label={__('Log In')}
/>
<Button button="link" onClick={handleChangeToSignIn} label={__('Log In')} />
</div>
<p className="help--card-actions">
<I18nMessage
@ -165,7 +86,7 @@ function UserEmailNew(props: Props) {
</Form>
</div>
}
nag={<>{errorMessage && <Nag type="error" relative message={<ErrorText>{errorMessage}</ErrorText>} />}</>}
nag={<>{'someMessage' && <Nag type="error" relative message={<ErrorText>{'someMessage'}</ErrorText>} />}</>}
/>
</div>
);

View file

@ -1,27 +0,0 @@
import { connect } from 'react-redux';
import {
selectEmailNewErrorMessage,
selectEmailToVerify,
selectEmailDoesNotExist,
selectEmailAlreadyExists,
selectUser,
selectEmailNewIsPending,
} from 'redux/selectors/user';
import { doUserCheckIfEmailExists, doClearEmailEntry } from 'redux/actions/user';
import { doSetWalletSyncPreference } from 'redux/actions/settings';
import UserEmailReturning from './view';
const select = state => ({
errorMessage: selectEmailNewErrorMessage(state),
emailToVerify: selectEmailToVerify(state),
emailDoesNotExist: selectEmailDoesNotExist(state),
emailExists: selectEmailAlreadyExists(state),
isPending: selectEmailNewIsPending(state),
user: selectUser(state),
});
export default connect(select, {
doUserCheckIfEmailExists,
doClearEmailEntry,
doSetWalletSyncPreference,
})(UserEmailReturning);

View file

@ -1,147 +0,0 @@
// @flow
import { CLOUD_CONNECT_SITE_NAME } from 'config';
import * as PAGES from 'constants/pages';
import React, { useState } from 'react';
import { FormField, Form } from 'component/common/form';
import Button from 'component/button';
import { EMAIL_REGEX } from 'constants/email';
import { useHistory } from 'react-router-dom';
import UserEmailVerify from 'component/userEmailVerify';
import Card from 'component/common/card';
import Nag from 'component/common/nag';
import classnames from 'classnames';
type Props = {
user: ?User,
errorMessage: ?string,
emailToVerify: ?string,
emailDoesNotExist: boolean,
doClearEmailEntry: () => void,
doUserSignIn: (string, ?string) => void,
doUserCheckIfEmailExists: (string) => void,
doSetWalletSyncPreference: (boolean) => void,
doSetClientSetting: (string, boolean, ?boolean) => void,
isPending: boolean,
};
function UserEmailReturning(props: Props) {
const {
user,
errorMessage,
doUserCheckIfEmailExists,
emailToVerify,
doClearEmailEntry,
emailDoesNotExist,
doSetWalletSyncPreference,
isPending,
} = props;
const { push, location } = useHistory();
const urlParams = new URLSearchParams(location.search);
const emailFromUrl = urlParams.get('email');
const emailExistsFromUrl = urlParams.get('email_exists');
const defaultEmail = emailFromUrl ? decodeURIComponent(emailFromUrl) : '';
const hasPasswordSet = user && user.password_set;
const [email, setEmail] = useState(defaultEmail);
const [syncEnabled, setSyncEnabled] = useState(true);
const valid = email.match(EMAIL_REGEX);
const showEmailVerification = emailToVerify || hasPasswordSet;
function handleSubmit() {
// @if TARGET='app'
doSetWalletSyncPreference(syncEnabled);
// @endif
doUserCheckIfEmailExists(email);
}
function handleChangeToSignIn() {
doClearEmailEntry();
let url = `/$/${PAGES.AUTH}`;
const urlParams = new URLSearchParams(location.search);
urlParams.delete('email_exists');
urlParams.delete('email');
if (email) {
urlParams.set('email', encodeURIComponent(email));
}
push(`${url}?${urlParams.toString()}`);
}
return (
<div className={classnames('main__sign-in')}>
{showEmailVerification ? (
<UserEmailVerify />
) : (
<Card
title={__('Cloud Connect')}
subtitle={__('Log in to %CLOUD_CONNECT_SITE_NAME%', { CLOUD_CONNECT_SITE_NAME })}
actions={
<div>
<Form onSubmit={handleSubmit} className="section">
<FormField
autoFocus={!emailExistsFromUrl}
placeholder={__('yourstruly@example.com')}
type="email"
id="username"
autoComplete="on"
name="sign_in_email"
label={__('Email')}
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{/* @if TARGET='app' */}
<FormField
type="checkbox"
name="sync_checkbox"
label={
<React.Fragment>
{__('Backup your account and wallet data.')}{' '}
<Button button="link" href="https://lbry.com/faq/account-sync" label={__('Learn More')} />
</React.Fragment>
}
checked={syncEnabled}
onChange={() => setSyncEnabled(!syncEnabled)}
/>
{/* @endif */}
<div className="section__actions">
<Button
autoFocus={emailExistsFromUrl}
button="primary"
type="submit"
label={__('Log In')}
disabled={!email || !valid || isPending}
/>
<Button button="link" onClick={handleChangeToSignIn} label={__('Sign Up')} />
</div>
</Form>
</div>
}
nag={
<>
{!emailDoesNotExist && emailExistsFromUrl && (
<Nag type="helpful" relative message={__('That email is already in use. Did you mean to log in?')} />
)}
{emailDoesNotExist && (
<Nag
type="helpful"
relative
message={__("We can't find that email. Did you mean to sign up?")}
actionText={__('Sign Up')}
/>
)}
{!emailExistsFromUrl && !emailDoesNotExist && errorMessage && (
<Nag type="error" relative message={errorMessage} />
)}
</>
}
/>
)}
</div>
);
}
export default UserEmailReturning;

View file

@ -1,25 +0,0 @@
import { connect } from 'react-redux';
import { doUserResendVerificationEmail, doUserCheckEmailVerified } from 'redux/actions/user';
import {
selectEmailToVerify,
selectEmailAlreadyExists,
selectUser,
selectResendingVerificationEmail,
} from 'redux/selectors/user';
import { doToast } from 'redux/actions/notifications';
import UserEmailVerify from './view';
const select = state => ({
email: selectEmailToVerify(state),
isReturningUser: selectEmailAlreadyExists(state),
user: selectUser(state),
resendingEmail: selectResendingVerificationEmail(state),
});
const perform = dispatch => ({
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
checkEmailVerified: () => dispatch(doUserCheckEmailVerified()),
toast: message => dispatch(doToast({ message })),
});
export default connect(select, perform)(UserEmailVerify);

View file

@ -1,121 +0,0 @@
// @flow
import * as React from 'react';
import Button from 'component/button';
import UserSignOutButton from 'component/userSignOutButton';
import I18nMessage from 'component/i18nMessage';
import Card from 'component/common/card';
import { SITE_HELP_EMAIL } from 'config';
const THIRTY_SECONDS_IN_MS = 30000;
type Props = {
email: string,
isReturningUser: boolean,
resendVerificationEmail: (string) => void,
resendingEmail: boolean,
checkEmailVerified: () => void,
toast: (string) => void,
user: {
has_verified_email: boolean,
},
};
type State = {
wait: boolean,
};
class UserEmailVerify extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.emailVerifyCheckInterval = null;
this.state = { wait: false };
(this: any).handleResendVerificationEmail = this.handleResendVerificationEmail.bind(this);
}
componentDidMount() {
this.emailVerifyCheckInterval = setInterval(() => {
this.checkIfVerified();
}, 5000);
}
componentDidUpdate() {
const { user } = this.props;
if (this.emailVerifyCheckInterval && user && user.has_verified_email) {
clearInterval(this.emailVerifyCheckInterval);
}
}
componentWillUnmount() {
if (this.emailVerifyCheckInterval) {
clearInterval(this.emailVerifyCheckInterval);
}
}
handleResendVerificationEmail() {
const { email, resendVerificationEmail, toast } = this.props;
if (!this.state.wait) {
resendVerificationEmail(email);
toast(__('New email sent.'));
this.setState({
wait: true,
});
setTimeout(() => this.setState({ wait: false }), THIRTY_SECONDS_IN_MS);
} else {
toast(__('Please wait a bit longer before requesting again.'));
}
}
checkIfVerified() {
const { checkEmailVerified } = this.props;
checkEmailVerified();
}
emailVerifyCheckInterval: ?IntervalID;
render() {
const { email, isReturningUser, resendingEmail } = this.props;
return (
<div className="main__sign-up">
<Card
title={isReturningUser ? __('Check Your email') : __('Confirm your account')}
subtitle={
<p>
{__(
'We just sent an email to %email% with a link for you to %verify_text%. Remember to check other email folders like spam or promotions.',
{
email,
verify_text: isReturningUser ? __('log in') : __('verify your account'),
}
)}
</p>
}
actions={
<React.Fragment>
<div className="section__actions">
<Button
button="primary"
label={__('Resend Link')}
onClick={this.handleResendVerificationEmail}
disabled={resendingEmail}
/>
<UserSignOutButton label={__('Start Over')} />
</div>
<p className="help--card-actions">
<I18nMessage
tokens={{
help_link: <Button button="link" href={`mailto:${SITE_HELP_EMAIL}`} label={`${SITE_HELP_EMAIL}`} />,
chat_link: <Button button="link" href="https://chat.lbry.com" label={__('chat')} />,
}}
>
Email %help_link% or join our %chat_link% if you encounter any trouble verifying.
</I18nMessage>
</p>
</React.Fragment>
}
/>
</div>
);
}
}
export default UserEmailVerify;

View file

@ -1,12 +1,9 @@
import { connect } from 'react-redux';
import { selectUser, selectEmailToVerify } from 'redux/selectors/user';
import { selectCreatingChannel, selectMyChannelClaims, selectCreateChannelError } from 'redux/selectors/claims';
import { doCreateChannel } from 'redux/actions/claims';
import UserFirstChannel from './view';
const select = (state) => ({
email: selectEmailToVerify(state),
user: selectUser(state),
channels: selectMyChannelClaims(state),
creatingChannel: selectCreatingChannel(state),
createChannelError: selectCreateChannelError(state),

View file

@ -15,22 +15,12 @@ type Props = {
creatingChannel: boolean,
createChannelError: string,
claimingReward: boolean,
user: User,
doToggleInterestedInYoutubeSync: () => void,
};
function UserFirstChannel(props: Props) {
const {
createChannel,
creatingChannel,
claimingReward,
user,
createChannelError,
doToggleInterestedInYoutubeSync,
} = props;
const { primary_email: primaryEmail } = user;
const initialChannel = primaryEmail ? primaryEmail.split('@')[0] : '';
const [channel, setChannel] = useState(initialChannel);
const { createChannel, creatingChannel, claimingReward, createChannelError, doToggleInterestedInYoutubeSync } = props;
const [channel, setChannel] = useState(''); // maybe recommend channel based on email when we have it
const [nameError, setNameError] = useState(undefined);
function handleCreateChannel() {

View file

@ -1,24 +0,0 @@
import { connect } from 'react-redux';
import {
selectPasswordResetSuccess,
selectPasswordResetIsPending,
selectPasswordResetError,
selectEmailToVerify,
} from 'redux/selectors/user';
import { doUserPasswordReset, doClearPasswordEntry, doClearEmailEntry } from 'redux/actions/user';
import { doToast } from 'redux/actions/notifications';
import UserSignIn from './view';
const select = state => ({
passwordResetSuccess: selectPasswordResetSuccess(state),
passwordResetIsPending: selectPasswordResetIsPending(state),
passwordResetError: selectPasswordResetError(state),
emailToVerify: selectEmailToVerify(state),
});
export default connect(select, {
doUserPasswordReset,
doToast,
doClearPasswordEntry,
doClearEmailEntry,
})(UserSignIn);

View file

@ -1,112 +0,0 @@
// @flow
import * as PAGES from 'constants/pages';
import React from 'react';
import { useHistory } from 'react-router-dom';
import Card from 'component/common/card';
import Spinner from 'component/spinner';
import { Form, FormField } from 'component/common/form';
import { EMAIL_REGEX } from 'constants/email';
import ErrorText from 'component/common/error-text';
import Button from 'component/button';
import Nag from 'component/common/nag';
type Props = {
user: ?User,
doToast: ({ message: string }) => void,
doUserPasswordReset: string => void,
doClearPasswordEntry: () => void,
doClearEmailEntry: () => void,
passwordResetPending: boolean,
passwordResetSuccess: boolean,
passwordResetError: ?string,
emailToVerify: ?string,
};
function UserPasswordReset(props: Props) {
const {
doUserPasswordReset,
passwordResetPending,
passwordResetError,
passwordResetSuccess,
doToast,
doClearPasswordEntry,
doClearEmailEntry,
emailToVerify,
} = props;
const { location, push, goBack } = useHistory();
const [email, setEmail] = React.useState(emailToVerify || '');
const valid = email.match(EMAIL_REGEX);
const restartAtSignInPage = location.pathname === `/$/${PAGES.AUTH_SIGNIN}`;
function handleSubmit() {
if (email) {
doUserPasswordReset(email);
}
}
function handleRestart() {
setEmail('');
doClearPasswordEntry();
doClearEmailEntry();
if (restartAtSignInPage) {
push(`/$/${PAGES.AUTH_SIGNIN}`);
} else {
goBack();
}
}
React.useEffect(() => {
if (passwordResetSuccess) {
doToast({
message: __('Email sent!'),
});
}
}, [passwordResetSuccess, doToast]);
return (
<section className="main__sign-in">
<Card
title={__('Reset your password')}
actions={
<div>
<Form onSubmit={handleSubmit} className="section">
<FormField
autoFocus
disabled={passwordResetSuccess}
placeholder={__('yourstruly@example.com')}
type="email"
name="sign_in_email"
id="username"
autoComplete="on"
label={__('Email')}
value={email}
onChange={e => setEmail(e.target.value)}
/>
<div className="section__actions">
<Button
button="primary"
type="submit"
label={passwordResetPending ? __('Resetting') : __('Reset Password')}
disabled={!email || !valid || passwordResetPending || passwordResetSuccess}
/>
<Button button="link" label={__('Cancel')} onClick={handleRestart} />
{passwordResetPending && <Spinner type="small" />}
</div>
</Form>
</div>
}
nag={
<React.Fragment>
{passwordResetError && <Nag type="error" relative message={<ErrorText>{passwordResetError}</ErrorText>} />}
{passwordResetSuccess && (
<Nag type="helpful" relative message={__('Check your email for a link to reset your password.')} />
)}
</React.Fragment>
}
/>
</section>
);
}
export default UserPasswordReset;

View file

@ -1,16 +0,0 @@
import { connect } from 'react-redux';
import { doClearEmailEntry, doUserFetch } from 'redux/actions/user';
import { doToast } from 'redux/actions/notifications';
import UserSignIn from './view';
const select = state => ({
// passwordSetSuccess: selectPasswordSetSuccess(state),
// passwordSetIsPending: selectPasswordSetIsPending(state),
// passwordSetError: selectPasswordSetError(state),
});
export default connect(select, {
doToast,
doClearEmailEntry,
doUserFetch,
})(UserSignIn);

View file

@ -1,108 +0,0 @@
// @flow
import * as PAGES from 'constants/pages';
import React from 'react';
import { Lbryio } from 'lbryinc';
import { useHistory } from 'react-router';
import Card from 'component/common/card';
import { Form, FormField } from 'component/common/form';
import ErrorText from 'component/common/error-text';
import Button from 'component/button';
import Nag from 'component/common/nag';
import Spinner from 'component/spinner';
type Props = {
user: ?User,
doClearEmailEntry: () => void,
doUserFetch: () => void,
doToast: ({ message: string }) => void,
history: { push: string => void },
location: { search: string },
passwordSetPending: boolean,
passwordSetError: ?string,
};
function UserPasswordReset(props: Props) {
const { doClearEmailEntry, doToast, doUserFetch } = props;
const { location, push } = useHistory();
const urlParams = new URLSearchParams(location.search);
const email = urlParams.get('email');
const authToken = urlParams.get('auth_token');
const verificationToken = urlParams.get('verification_token');
const [password, setPassword] = React.useState('');
const [error, setError] = React.useState();
const [loading, setLoading] = React.useState(false);
function handleSubmit() {
setLoading(true);
Lbryio.call('user_email', 'confirm', {
email: email,
verification_token: verificationToken,
})
.then(() =>
Lbryio.call(
'user_password',
'set',
{
auth_token: authToken,
new_password: password,
},
'post'
)
)
.then(doUserFetch)
.then(() => {
setLoading(false);
doToast({
message: __('Password successfully changed!'),
});
push(`/`);
})
.catch(error => {
setLoading(false);
setError(error.message);
});
}
function handleRestart() {
doClearEmailEntry();
push(`/$/${PAGES.AUTH_SIGNIN}`);
}
return (
<section className="main__sign-in">
<Card
title={__('Choose a new password')}
subtitle={__('Setting a new password for %email%', { email })}
actions={
<div>
<Form onSubmit={handleSubmit} className="section">
<FormField
autoFocus
type="password"
name="password_set"
label={__('New Password')}
value={password}
onChange={e => setPassword(e.target.value)}
/>
<div className="section__actions">
<Button
button="primary"
type="submit"
label={loading ? __('Updating Password') : __('Update Password')}
disabled={!password || loading}
/>
<Button button="link" label={__('Cancel')} onClick={handleRestart} />
{loading && <Spinner type="small" />}
</div>
</Form>
</div>
}
nag={error && <Nag type="error" relative message={<ErrorText>{error}</ErrorText>} />}
/>
</section>
);
}
export default UserPasswordReset;

View file

@ -1,15 +0,0 @@
import { connect } from 'react-redux';
import { selectUser, selectUserIsPending, selectEmailToVerify, selectPasswordExists } from 'redux/selectors/user';
import { doUserSignIn } from 'redux/actions/user';
import UserSignIn from './view';
const select = state => ({
user: selectUser(state),
userFetchPending: selectUserIsPending(state),
emailToVerify: selectEmailToVerify(state),
passwordExists: selectPasswordExists(state),
});
export default connect(select, {
doUserSignIn,
})(UserSignIn);

View file

@ -1,58 +0,0 @@
// @flow
import React from 'react';
import { withRouter } from 'react-router';
import UserEmailReturning from 'component/userEmailReturning';
import UserSignInPassword from 'component/userSignInPassword';
import Spinner from 'component/spinner';
type Props = {
user: ?User,
history: { push: string => void, replace: string => void },
location: { search: string },
userFetchPending: boolean,
doUserSignIn: string => void,
emailToVerify: ?string,
passwordExists: boolean,
};
function UserSignIn(props: Props) {
const { user, location, history, doUserSignIn, userFetchPending, emailToVerify, passwordExists } = props;
const { search } = location;
const urlParams = new URLSearchParams(search);
const [emailOnlyLogin, setEmailOnlyLogin] = React.useState(false);
const hasVerifiedEmail = user && user.has_verified_email;
const redirect = urlParams.get('redirect');
const showLoading = userFetchPending;
const showEmail = !passwordExists || emailOnlyLogin;
const showPassword = !showEmail && emailToVerify && passwordExists;
React.useEffect(() => {
if (hasVerifiedEmail || (!showEmail && !showPassword && !showLoading)) {
history.replace(redirect || '/');
}
}, [showEmail, showPassword, showLoading, hasVerifiedEmail]);
React.useEffect(() => {
if (emailToVerify && emailOnlyLogin) {
doUserSignIn(emailToVerify);
}
}, [emailToVerify, emailOnlyLogin, doUserSignIn]);
return (
<section>
{(showEmail || showPassword) && (
<div>
{showEmail && <UserEmailReturning />}
{showPassword && <UserSignInPassword onHandleEmailOnly={() => setEmailOnlyLogin(true)} />}
</div>
)}
{!showEmail && !showPassword && showLoading && (
<div className="main--empty">
<Spinner delayed />
</div>
)}
</section>
);
}
export default withRouter(UserSignIn);

View file

@ -1,23 +0,0 @@
import { connect } from 'react-redux';
import {
selectUser,
selectUserIsPending,
selectEmailToVerify,
selectEmailNewErrorMessage,
selectEmailNewIsPending,
} from 'redux/selectors/user';
import { doUserSignIn, doClearEmailEntry } from 'redux/actions/user';
import UserSignIn from './view';
const select = state => ({
user: selectUser(state),
userFetchPending: selectUserIsPending(state),
emailToVerify: selectEmailToVerify(state),
errorMessage: selectEmailNewErrorMessage(state),
isPending: selectEmailNewIsPending(state),
});
export default connect(select, {
doUserSignIn,
doClearEmailEntry,
})(UserSignIn);

View file

@ -1,67 +0,0 @@
// @flow
import { SITE_NAME } from 'config';
import React, { useState } from 'react';
import { FormField, Form } from 'component/common/form';
import Button from 'component/button';
import Card from 'component/common/card';
import Nag from 'component/common/nag';
import UserPasswordReset from 'component/userPasswordReset';
type Props = {
errorMessage: ?string,
emailToVerify: ?string,
doClearEmailEntry: () => void,
doUserSignIn: (string, ?string) => void,
onHandleEmailOnly: () => void,
isPending: boolean,
};
export default function UserSignInPassword(props: Props) {
const { errorMessage, doUserSignIn, emailToVerify, onHandleEmailOnly, isPending } = props;
const [password, setPassword] = useState('');
const [forgotPassword, setForgotPassword] = React.useState(false);
function handleSubmit() {
if (emailToVerify) {
doUserSignIn(emailToVerify, password);
}
}
function handleChangeToSignIn() {
onHandleEmailOnly();
}
return (
<div className="main__sign-in">
{forgotPassword ? (
<UserPasswordReset />
) : (
<Card
title={__('Enter your %SITE_NAME% password', { SITE_NAME })}
subtitle={__('Logging in as %email%', { email: emailToVerify })}
actions={
<Form onSubmit={handleSubmit} className="section">
<FormField
autoFocus
type="password"
name="sign_in_password"
id="password"
autoComplete="on"
label={__('Password')}
value={password}
onChange={(e) => setPassword(e.target.value)}
helper={<Button button="link" label={__('Forgot Password?')} onClick={() => setForgotPassword(true)} />}
/>
<div className="section__actions">
<Button button="primary" type="submit" label={__('Continue')} disabled={!password || isPending} />
<Button button="link" onClick={handleChangeToSignIn} label={__('Use Magic Link')} />
</div>
</Form>
}
nag={errorMessage && <Nag type="error" relative message={errorMessage} />}
/>
)}
</div>
);
}

View file

@ -1,12 +0,0 @@
import { connect } from 'react-redux';
import { doSignOut } from 'redux/actions/app';
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
import UserSignOutButton from './view';
const select = state => ({});
export default connect(select, {
doSignOut,
doClearEmailEntry,
doClearPasswordEntry,
})(UserSignOutButton);

View file

@ -1,29 +0,0 @@
// @flow
import React from 'react';
import Button from 'component/button';
type Props = {
button: string,
label?: string,
doSignOut: () => void,
doClearEmailEntry: () => void,
doClearPasswordEntry: () => void,
};
function UserSignOutButton(props: Props) {
const { button = 'link', doSignOut, doClearEmailEntry, doClearPasswordEntry, label } = props;
return (
<Button
button={button}
label={label || __('Sign Out')}
onClick={() => {
doClearPasswordEntry();
doClearEmailEntry();
doSignOut();
}}
/>
);
}
export default UserSignOutButton;

View file

@ -1,65 +0,0 @@
import REWARD_TYPES from 'rewards';
import { connect } from 'react-redux';
import { selectGetSyncIsPending, selectSyncHash, selectPrefsReady } from 'redux/selectors/sync';
import { doClaimRewardType } from 'redux/actions/rewards';
import { doSetClientSetting } from 'redux/actions/settings';
import { selectClaimedRewards, makeSelectIsRewardClaimPending } from 'redux/selectors/rewards';
import { doUserFetch } from 'redux/actions/user';
import {
selectUserIsPending,
selectYoutubeChannels,
selectEmailToVerify,
selectUser,
selectAccessToken,
} from 'redux/selectors/user';
import { selectMyChannelClaims, selectFetchingMyChannels, selectCreatingChannel } from 'redux/selectors/claims';
import { selectBalance } from 'redux/selectors/wallet';
import * as SETTINGS from 'constants/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectInterestedInYoutubeSync } from 'redux/selectors/app';
import { doToggleInterestedInYoutubeSync } from 'redux/actions/app';
import UserSignIn from './view';
const select = (state) => ({
emailToVerify: selectEmailToVerify(state),
user: selectUser(state),
accessToken: selectAccessToken(state),
channels: selectMyChannelClaims(state),
claimedRewards: selectClaimedRewards(state),
claimingReward: makeSelectIsRewardClaimPending()(state, {
reward_type: REWARD_TYPES.TYPE_CONFIRM_EMAIL,
}),
balance: selectBalance(state),
fetchingChannels: selectFetchingMyChannels(state),
youtubeChannels: selectYoutubeChannels(state),
userFetchPending: selectUserIsPending(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
followingAcknowledged: makeSelectClientSetting(SETTINGS.FOLLOWING_ACKNOWLEDGED)(state),
tagsAcknowledged: makeSelectClientSetting(SETTINGS.TAGS_ACKNOWLEDGED)(state),
rewardsAcknowledged: makeSelectClientSetting(SETTINGS.REWARDS_ACKNOWLEDGED)(state),
syncingWallet: selectGetSyncIsPending(state),
hasSynced: Boolean(selectSyncHash(state)),
creatingChannel: selectCreatingChannel(state),
interestedInYoutubeSync: selectInterestedInYoutubeSync(state),
prefsReady: selectPrefsReady(state),
});
const perform = (dispatch) => ({
fetchUser: () => dispatch(doUserFetch()),
claimConfirmEmailReward: () =>
dispatch(
doClaimRewardType(REWARD_TYPES.TYPE_CONFIRM_EMAIL, {
notifyError: false,
})
),
claimNewUserReward: () =>
dispatch(
doClaimRewardType(REWARD_TYPES.NEW_USER, {
notifyError: false,
})
),
setClientSetting: (setting, value, pushToPrefs) => dispatch(doSetClientSetting(setting, value, pushToPrefs)),
doToggleInterestedInYoutubeSync: () => dispatch(doToggleInterestedInYoutubeSync()),
});
export default connect(select, perform)(UserSignIn);

View file

@ -1,232 +0,0 @@
// @flow
import * as PAGES from 'constants/pages';
import * as SETTINGS from 'constants/settings';
import React from 'react';
import classnames from 'classnames';
import { useHistory } from 'react-router';
import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify';
import UserChannelFollowIntro from 'component/userChannelFollowIntro';
import UserTagFollowIntro from 'component/userTagFollowIntro';
import REWARDS from 'rewards';
import Spinner from 'component/spinner';
import useFetched from 'effects/use-fetched';
import usePrevious from 'effects/use-previous';
import { SHOW_TAGS_INTRO } from 'config';
const REDIRECT_PARAM = 'redirect';
const REDIRECT_IMMEDIATELY_PARAM = 'immediate';
const STEP_PARAM = 'step';
type Props = {
user: ?User,
emailToVerify: ?string,
channels: ?Array<string>,
balance: ?number,
fetchingChannels: boolean,
claimingReward: boolean,
claimConfirmEmailReward: () => void,
claimNewUserReward: () => void,
fetchUser: () => void,
claimedRewards: Array<Reward>,
youtubeChannels: Array<any>,
syncEnabled: boolean,
hasSynced: boolean,
syncingWallet: boolean,
creatingChannel: boolean,
setClientSetting: (string, boolean, ?boolean) => void,
followingAcknowledged: boolean,
tagsAcknowledged: boolean,
rewardsAcknowledged: boolean,
interestedInYoutubeSync: boolean,
doToggleInterestedInYoutubeSync: () => void,
prefsReady: boolean,
};
function UserSignUp(props: Props) {
const {
emailToVerify,
user,
claimingReward,
claimedRewards,
claimConfirmEmailReward,
claimNewUserReward,
balance,
fetchUser,
syncEnabled,
syncingWallet,
hasSynced,
fetchingChannels,
creatingChannel,
followingAcknowledged,
tagsAcknowledged,
rewardsAcknowledged,
setClientSetting,
interestedInYoutubeSync,
doToggleInterestedInYoutubeSync,
prefsReady,
} = props;
const {
location: { search, pathname },
replace,
} = useHistory();
const urlParams = new URLSearchParams(search);
const redirect = urlParams.get(REDIRECT_PARAM);
const step = urlParams.get(STEP_PARAM);
const shouldRedirectImmediately = urlParams.get(REDIRECT_IMMEDIATELY_PARAM);
const [initialSignInStep, setInitialSignInStep] = React.useState();
const hasVerifiedEmail = user && user.has_verified_email;
const passwordSet = user && user.password_set;
const hasFetchedReward = useFetched(claimingReward);
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
const hasClaimedEmailAward = claimedRewards.some((reward) => reward.reward_type === REWARDS.TYPE_CONFIRM_EMAIL);
// Complexity warning
// We can't just check if we are currently fetching something
// We may want to keep a component rendered while something is being fetched, instead of replacing it with the large spinner
// The verbose variable names are an attempt to alleviate _some_ of the confusion from handling all edge cases that come from
// reward claiming, channel creation, account syncing, and youtube transfer
// The possible screens for the sign in flow
const showEmail = !hasVerifiedEmail;
const showEmailVerification = (emailToVerify && !hasVerifiedEmail) || (!hasVerifiedEmail && passwordSet);
const showFollowIntro = step === 'channels' || (hasVerifiedEmail && !followingAcknowledged);
const showTagsIntro = SHOW_TAGS_INTRO && (step === 'tags' || (hasVerifiedEmail && !tagsAcknowledged));
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && !showFollowIntro && !showTagsIntro && !rewardsAcknowledged;
const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet || creatingChannel;
const isWaitingForSomethingToFinish =
// If the user has claimed the email award, we need to wait until the balance updates sometime in the future
(!hasFetchedReward && !hasClaimedEmailAward) || (syncEnabled && !hasSynced);
const showLoadingSpinner =
canHijackSignInFlowWithSpinner && (isCurrentlyFetchingSomething || isWaitingForSomethingToFinish);
function setSettingAndSync(setting, value) {
setClientSetting(setting, value, true);
}
React.useEffect(() => {
fetchUser();
}, [fetchUser]);
React.useEffect(() => {
if (previousHasVerifiedEmail === false && hasVerifiedEmail && prefsReady) {
setSettingAndSync(SETTINGS.FIRST_RUN_STARTED, true);
}
}, [hasVerifiedEmail, previousHasVerifiedEmail, prefsReady]);
React.useEffect(() => {
// Don't claim the reward if sync is enabled until after a sync has been completed successfully
// If we do it before, we could end up trying to sync a wallet with a non-zero balance which will fail to sync
const delayForSync = syncEnabled && !hasSynced;
if (hasVerifiedEmail && !hasClaimedEmailAward && !hasFetchedReward && !delayForSync) {
claimConfirmEmailReward();
}
}, [
hasVerifiedEmail,
claimConfirmEmailReward,
hasClaimedEmailAward,
hasFetchedReward,
syncEnabled,
hasSynced,
balance,
]);
// Loop through this list from the end, until it finds a matching component
// If it never finds one, assume the user has completed every step and redirect them
const SIGN_IN_FLOW = [
showEmail && (
<UserEmailNew
interestedInYoutubSync={interestedInYoutubeSync}
doToggleInterestedInYoutubeSync={doToggleInterestedInYoutubeSync}
/>
),
showEmailVerification && <UserEmailVerify />,
showFollowIntro && (
<UserChannelFollowIntro
onContinue={() => {
if (urlParams.get('reset_scroll')) {
urlParams.delete('reset_scroll');
urlParams.append('reset_scroll', '2');
}
urlParams.delete(STEP_PARAM);
setSettingAndSync(SETTINGS.FOLLOWING_ACKNOWLEDGED, true);
replace(`${pathname}?${urlParams.toString()}`);
}}
onBack={() => {
if (urlParams.get('reset_scroll')) {
urlParams.delete('reset_scroll');
urlParams.append('reset_scroll', '3');
}
setSettingAndSync(SETTINGS.FOLLOWING_ACKNOWLEDGED, false);
replace(`${pathname}?${urlParams.toString()}`);
}}
/>
),
showTagsIntro && (
<UserTagFollowIntro
onContinue={() => {
let url = `/$/${PAGES.AUTH}?reset_scroll=1&${STEP_PARAM}=channels`;
if (redirect) {
url += `&${REDIRECT_PARAM}=${redirect}`;
}
if (shouldRedirectImmediately) {
url += `&${REDIRECT_IMMEDIATELY_PARAM}=true`;
}
replace(url);
setSettingAndSync(SETTINGS.TAGS_ACKNOWLEDGED, true);
}}
/>
),
showLoadingSpinner && (
<div className="main--empty">
<Spinner />
</div>
),
];
// $FlowFixMe
function getSignInStep() {
for (var i = SIGN_IN_FLOW.length - 1; i > -1; i--) {
const Component = SIGN_IN_FLOW[i];
if (Component) {
// If we want to redirect immediately,
// remember the first step so we can redirect once a new step has been reached
// Ignore the loading step
if (redirect && shouldRedirectImmediately) {
if (!initialSignInStep) {
setInitialSignInStep(i);
} else if (i !== initialSignInStep && i !== SIGN_IN_FLOW.length - 1) {
replace(redirect);
}
}
const scrollableSteps = [2, 4, 5];
const isScrollable = scrollableSteps.includes(i);
return [Component, isScrollable];
}
}
return [undefined, false];
}
const [componentToRender, isScrollable] = getSignInStep();
React.useEffect(() => {
if (!componentToRender) {
claimNewUserReward();
}
}, [componentToRender, claimNewUserReward]);
if (!componentToRender) {
replace(redirect || '/');
}
return (
<section className={classnames('main--contained', { 'main--hoisted': isScrollable })}>{componentToRender}</section>
);
}
export default UserSignUp;

View file

@ -6,23 +6,15 @@ import {
} from 'redux/selectors/collections';
import * as SETTINGS from 'constants/settings';
import * as COLLECTIONS_CONSTS from 'constants/collections';
import {
doChangeVolume,
doChangeMute,
doAnalyticsView,
doAnalyticsBuffer,
doAnaltyicsPurchaseEvent,
} from 'redux/actions/app';
import { doChangeVolume, doChangeMute, doAnalyticsView, doAnaltyicsPurchaseEvent } from 'redux/actions/app';
import { selectVolume, selectMute } from 'redux/selectors/app';
import { savePosition, clearPosition, doPlayUri, doSetPlayingUri } from 'redux/actions/content';
import { makeSelectContentPositionForUri, makeSelectIsPlayerFloating, selectPlayingUri } from 'redux/selectors/content';
import { selectRecommendedContentForUri } from 'redux/selectors/search';
import VideoViewer from './view';
import { withRouter } from 'react-router';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
import { selectDaemonSettings, makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
import { toggleVideoTheaterMode, toggleAutoplayNext, doSetClientSetting } from 'redux/actions/settings';
import { selectUser } from 'redux/selectors/user';
const select = (state, props) => {
const { search } = props.location;
@ -30,7 +22,6 @@ const select = (state, props) => {
const uri = props.uri;
// TODO: eventually this should be received from DB and not local state (https://github.com/lbryio/lbry-desktop/issues/6796)
const position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(uri)(state);
const userId = selectUser(state) && selectUser(state).id;
const playingUri = selectPlayingUri(state);
const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || (playingUri && playingUri.collectionId);
const isMarkdownOrComment = playingUri && (playingUri.source === 'markdown' || playingUri.source === 'comment');
@ -47,7 +38,6 @@ const select = (state, props) => {
return {
position,
userId,
collectionId,
nextRecommendedUri,
previousListUri,
@ -71,8 +61,6 @@ const perform = (dispatch) => ({
clearPosition: (uri) => dispatch(clearPosition(uri)),
changeMute: (muted) => dispatch(doChangeMute(muted)),
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
doAnalyticsBuffer: (uri, bufferData) => dispatch(doAnalyticsBuffer(uri, bufferData)),
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()),
toggleAutoplayNext: () => dispatch(toggleAutoplayNext()),
setVideoPlaybackRate: (rate) => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)),

View file

@ -1,178 +0,0 @@
// Created by xander on 6/21/2021
import videojs from 'video.js';
import RecSys from 'extras/recsys/recsys';
const VERSION = '0.0.1';
/* RecSys */
const PlayerEvent = {
event: {
start: 0, // event types
stop: 1,
scrub: 2,
speed: 3,
},
};
function newRecsysPlayerEvent(eventType, offset, arg) {
if (arg) {
return {
event: eventType,
offset: offset,
arg: arg,
};
} else {
return {
event: eventType,
offset: offset,
};
}
}
const defaults = {
videoId: null,
userId: 0,
debug: false,
embedded: false,
};
const Component = videojs.getComponent('Component');
const registerPlugin = videojs.registerPlugin || videojs.plugin;
class RecsysPlugin extends Component {
constructor(player, options) {
super(player, options);
// Plugin started
if (options.debug) {
this.log(`Created recsys plugin for: videoId:${options.videoId}`);
}
// To help with debugging, we'll add a global vjs object with the video js player
window.vjs = player;
this.player = player;
this.lastTimeUpdate = null;
this.currentTimeUpdate = null;
this.inPause = false;
// Plugin event listeners
player.on('playing', (event) => this.onPlay(event));
player.on('pause', (event) => this.onPause(event));
player.on('ended', (event) => this.onEnded(event));
player.on('ratechange', (event) => this.onRateChange(event));
player.on('timeupdate', (event) => this.onTimeUpdate(event));
player.on('seeked', (event) => this.onSeeked(event));
// Event trigger to send recsys event
player.on('dispose', (event) => this.onDispose(event));
}
onPlay(event) {
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.start, this.player.currentTime());
this.log('onPlay', recsysEvent);
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent, this.options_.embedded);
this.inPause = false;
this.lastTimeUpdate = recsysEvent.offset;
}
onPause(event) {
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.stop, this.player.currentTime());
this.log('onPause', recsysEvent);
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
this.inPause = true;
}
onEnded(event) {
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.stop, this.player.currentTime());
this.log('onEnded', recsysEvent);
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
}
onRateChange(event) {
const recsysEvent = newRecsysPlayerEvent(
PlayerEvent.event.speed,
this.player.currentTime(),
this.player.playbackRate()
);
this.log('onRateChange', recsysEvent);
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
}
onTimeUpdate(event) {
const nextCurrentTime = this.player.currentTime();
if (!this.inPause && Math.abs(this.lastTimeUpdate - nextCurrentTime) < 0.5) {
// Don't update lastTimeUpdate if we are in a pause segment.
//
// However, if we aren't in a pause and the time jumped
// the onTimeUpdate event probably fired before the pause and seek.
// Don't update in that case, either.
this.lastTimeUpdate = this.currentTimeUpdate;
}
this.currentTimeUpdate = nextCurrentTime;
}
onSeeked(event) {
const curTime = this.player.currentTime();
// There are three patterns for seeking:
//
// Assuming the video is playing,
//
// 1. Dragging the player head emits: onPause -> onSeeked -> onSeeked -> ... -> onPlay
// 2. Key press left right emits: onSeeked -> onPlay
// 3. Clicking a position emits: onPause -> onSeeked -> onPlay
//
// If the video is NOT playing,
//
// 1. Dragging the player head emits: onSeeked
// 2. Key press left right emits: onSeeked
// 3. Clicking a position emits: onSeeked
const fromTime = this.lastTimeUpdate;
if (fromTime !== curTime) {
// This removes duplicates that aren't useful.
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.scrub, fromTime, curTime);
this.log('onSeeked', recsysEvent);
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
}
}
onDispose(event) {
RecSys.onPlayerDispose(this.options_.videoId, this.options_.embedded);
}
log(...args) {
if (this.options_.debug) {
console.log(`Recsys Player Debug:`, JSON.stringify(args));
}
}
}
videojs.registerComponent('recsys', RecsysPlugin);
const onPlayerReady = (player, options) => {
player.recsys = new RecsysPlugin(player, options);
};
/**
* Initialize the plugin.
*
* @function plugin
* @param {Object} [options={}]
*/
const plugin = function (options) {
this.ready(() => {
onPlayerReady(this, videojs.mergeOptions(defaults, options));
});
};
plugin.VERSION = VERSION;
registerPlugin('recsys', plugin);
export default plugin;

View file

@ -9,7 +9,7 @@ import eventTracking from 'videojs-event-tracking';
import * as OVERLAY from './overlays';
import './plugins/videojs-mobile-ui/plugin';
import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin';
import recsys from './plugins/videojs-recsys/plugin';
// import recsys from './plugins/videojs-recsys/plugin';
import qualityLevels from 'videojs-contrib-quality-levels';
import LbryVolumeBarClass from './lbry-volume-bar';
import keyboardShorcuts from './videojs-keyboard-shortcuts';
@ -51,10 +51,6 @@ type Props = {
autoplay: boolean,
autoplaySetting: boolean,
toggleVideoTheaterMode: () => void,
claimId: ?string,
userId: ?number,
// allowPreRoll: ?boolean,
shareTelemetry: boolean,
replay: boolean,
videoTheaterMode: boolean,
playNext: () => void,
@ -92,9 +88,9 @@ if (!Object.keys(videojs.getPlugins()).includes('qualityLevels')) {
videojs.registerPlugin('qualityLevels', qualityLevels);
}
if (!Object.keys(videojs.getPlugins()).includes('recsys')) {
videojs.registerPlugin('recsys', recsys);
}
// if (!Object.keys(videojs.getPlugins()).includes('recsys')) {
// videojs.registerPlugin('recsys', recsys);
// }
// ****************************************************************************
// VideoJs
@ -113,9 +109,6 @@ export default React.memo<Props>(function VideoJs(props: Props) {
isAudio,
onPlayerReady,
toggleVideoTheaterMode,
claimId,
userId,
shareTelemetry,
replay,
videoTheaterMode,
playNext,
@ -193,14 +186,6 @@ export default React.memo<Props>(function VideoJs(props: Props) {
displayCurrentQuality: true,
});
// Add recsys plugin
if (shareTelemetry) {
player.recsys({
videoId: claimId,
userId: userId,
});
}
// set playsinline for mobile
player.children_[0].setAttribute('playsinline', '');

View file

@ -36,16 +36,12 @@ type Props = {
autoplayNext: boolean,
desktopPlayStartTime?: number,
doAnalyticsView: (string, number) => Promise<any>,
doAnalyticsBuffer: (string, any) => void,
claimRewards: () => void,
savePosition: (string, number) => void,
clearPosition: (string) => void,
toggleVideoTheaterMode: () => void,
toggleAutoplayNext: () => void,
setVideoPlaybackRate: (number) => void,
userId: number,
homepageData?: { [string]: HomepageCat },
shareTelemetry: boolean,
isFloating: boolean,
doPlayUri: (string, string) => void,
collectionId: string,
@ -75,16 +71,12 @@ function VideoViewer(props: Props) {
volume,
autoplayNext,
doAnalyticsView,
doAnalyticsBuffer,
claimRewards,
savePosition,
clearPosition,
desktopPlayStartTime,
toggleVideoTheaterMode,
toggleAutoplayNext,
setVideoPlaybackRate,
userId,
shareTelemetry,
isFloating,
doPlayUri,
collectionId,
@ -94,7 +86,6 @@ function VideoViewer(props: Props) {
isMarkdownOrComment,
} = props;
const permanentUrl = claim && claim.permanent_url;
const claimId = claim && claim.claim_id;
const isAudio = contentType.includes('audio');
const forcePlayer = FORCE_CONTENT_TYPE_PLAYER.includes(contentType);
const { push } = useHistory();
@ -134,13 +125,6 @@ function VideoViewer(props: Props) {
};
}, [videoPlaybackRate]);
function doTrackingBuffered(e: Event, data: any) {
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
data.playerPoweredBy = response.headers.get('x-powered-by');
doAnalyticsBuffer(uri, data);
});
}
function doTrackingFirstPlay(e: Event, data: any) {
let timeToStart = data.secondsToLoad;
@ -150,30 +134,7 @@ function VideoViewer(props: Props) {
}
analytics.playerStartedEvent();
// convert bytes to bits, and then divide by seconds
const contentInBits = Number(claim.value.source.size) * 8;
const durationInSeconds = claim.value.video && claim.value.video.duration;
let bitrateAsBitsPerSecond;
if (durationInSeconds) {
bitrateAsBitsPerSecond = Math.round(contentInBits / durationInSeconds);
}
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
let playerPoweredBy = response.headers.get('x-powered-by') || '';
analytics.videoStartEvent(
claimId,
timeToStart,
playerPoweredBy,
userId,
claim.canonical_url,
this,
bitrateAsBitsPerSecond
);
});
doAnalyticsView(uri, timeToStart).then(() => {
claimRewards();
});
doAnalyticsView(uri, timeToStart);
}
const doPlay = useCallback(
@ -335,9 +296,6 @@ function VideoViewer(props: Props) {
// re-factoring.
player.on('loadedmetadata', () => restorePlaybackRate(player));
// used for tracking buffering for watchman
player.on('tracking:buffered', doTrackingBuffered);
// first play tracking, used for initializing the watchman api
player.on('tracking:firstplay', doTrackingFirstPlay);
player.on('ended', () => setEnded(true));
@ -397,9 +355,6 @@ function VideoViewer(props: Props) {
toggleVideoTheaterMode={toggleVideoTheaterMode}
autoplay
autoplaySetting={autoplayNext}
claimId={claimId}
userId={userId}
shareTelemetry={shareTelemetry}
replay={replay}
videoTheaterMode={videoTheaterMode}
playNext={doPlayNext}

View file

@ -14,7 +14,6 @@ import {
import { doFetchUtxoCounts, doUtxoConsolidate } from 'redux/actions/wallet';
import { doOpenModal } from 'redux/actions/app';
import { selectSyncHash } from 'redux/selectors/sync';
import { selectClaimedRewards } from 'redux/selectors/rewards';
import WalletBalance from './view';
const select = (state) => ({
@ -22,7 +21,6 @@ const select = (state) => ({
claimsBalance: selectClaimsBalance(state) || 0,
supportsBalance: selectSupportsBalance(state) || 0,
tipsBalance: selectTipsBalance(state) || 0,
rewards: selectClaimedRewards(state),
hasSynced: Boolean(selectSyncHash(state)),
fetchingUtxoCounts: selectIsFetchingUtxoCounts(state),
consolidatingUtxos: selectIsConsolidatingUtxos(state),

View file

@ -1,24 +0,0 @@
import { connect } from 'react-redux';
import { doClaimYoutubeChannels, doUserFetch, doCheckYoutubeTransfer } from 'redux/actions/user';
import {
selectYoutubeChannels,
selectYouTubeImportVideosComplete,
selectYouTubeImportPending,
selectUserIsPending,
} from 'redux/selectors/user';
import YoutubeChannelList from './view';
const select = state => ({
youtubeChannels: selectYoutubeChannels(state),
youtubeImportPending: selectYouTubeImportPending(state),
userFetchPending: selectUserIsPending(state),
videosImported: selectYouTubeImportVideosComplete(state),
});
const perform = dispatch => ({
claimChannels: () => dispatch(doClaimYoutubeChannels()),
updateUser: () => dispatch(doUserFetch()),
checkYoutubeTransfer: () => dispatch(doCheckYoutubeTransfer()),
});
export default connect(select, perform)(YoutubeChannelList);

View file

@ -1,235 +0,0 @@
// @flow
import { SITE_NAME, SITE_HELP_EMAIL } from 'config';
import * as ICONS from 'constants/icons';
import * as React from 'react';
import classnames from 'classnames';
import Button from 'component/button';
import ClaimPreview from 'component/claimPreview';
import Card from 'component/common/card';
import { YOUTUBE_STATUSES } from 'lbryinc';
import { buildURI } from 'util/lbryURI';
import Spinner from 'component/spinner';
import Icon from 'component/common/icon';
import I18nMessage from 'component/i18nMessage';
type Props = {
youtubeChannels: Array<any>,
youtubeImportPending: boolean,
claimChannels: () => void,
updateUser: () => void,
checkYoutubeTransfer: () => void,
videosImported: ?Array<number>, // [currentAmountImported, totalAmountToImport]
alwaysShow: boolean,
addNewChannel?: boolean,
};
export default function YoutubeTransferStatus(props: Props) {
const {
youtubeChannels,
youtubeImportPending,
claimChannels,
videosImported,
checkYoutubeTransfer,
updateUser,
alwaysShow = false,
addNewChannel,
} = props;
const hasChannels = youtubeChannels && youtubeChannels.length > 0;
const transferEnabled = youtubeChannels.some((status) => status.transferable);
const hasPendingTransfers = youtubeChannels.some(
(status) => status.transfer_state === YOUTUBE_STATUSES.YOUTUBE_SYNC_PENDING_TRANSFER
);
const isYoutubeTransferComplete =
hasChannels &&
youtubeChannels.every(
(channel) =>
channel.transfer_state === YOUTUBE_STATUSES.YOUTUBE_SYNC_COMPLETED_TRANSFER ||
channel.sync_status === YOUTUBE_STATUSES.YOUTUBE_SYNC_ABANDONDED
);
const isNotElligible =
hasChannels && youtubeChannels.every((channel) => channel.sync_status === YOUTUBE_STATUSES.YOUTUBE_SYNC_ABANDONDED);
let total;
let complete;
if (hasPendingTransfers && videosImported) {
complete = videosImported[0];
total = videosImported[1];
}
function getMessage(channel) {
const { transferable, transfer_state: transferState, sync_status: syncStatus } = channel;
if (!transferable) {
switch (transferState) {
case YOUTUBE_STATUSES.YOUTUBE_SYNC_NOT_TRANSFERRED:
return syncStatus[0].toUpperCase() + syncStatus.slice(1);
case YOUTUBE_STATUSES.YOUTUBE_SYNC_PENDING_TRANSFER:
return __('Transfer in progress');
case YOUTUBE_STATUSES.YOUTUBE_SYNC_COMPLETED_TRANSFER:
return __('Completed transfer');
case YOUTUBE_STATUSES.YOUTUBE_SYNC_ABANDONDED:
return __('This channel not eligible to by synced');
}
} else {
return __('Ready to transfer');
}
}
React.useEffect(() => {
// If a channel is transferable, there's nothing to check
if (hasPendingTransfers) {
checkYoutubeTransfer();
let interval = setInterval(() => {
checkYoutubeTransfer();
updateUser();
}, 60 * 1000);
return () => {
clearInterval(interval);
};
}
}, [hasPendingTransfers, checkYoutubeTransfer, updateUser]);
return (
(alwaysShow || (hasChannels && !isYoutubeTransferComplete)) && (
<Card
title={
isNotElligible
? __('Process complete')
: isYoutubeTransferComplete
? __('Transfer complete')
: youtubeChannels.length > 1
? __('Your YouTube channels')
: __('Your YouTube channel')
}
subtitle={
<span>
{hasPendingTransfers &&
__('Your videos are currently being transferred. There is nothing else for you to do.')}
{transferEnabled && !hasPendingTransfers && __('Your videos are ready to be transferred.')}
{!transferEnabled &&
!hasPendingTransfers &&
!isYoutubeTransferComplete &&
!isNotElligible &&
__('Please check back later. This may take up to 1 week.')}
{isYoutubeTransferComplete && !isNotElligible && __('View your channel or choose a new channel to sync.')}
{isNotElligible && (
<I18nMessage
tokens={{
here: <Button button="link" href="https://lbry.com/faq/youtube" label={__('here')} />,
email: SITE_HELP_EMAIL,
}}
>
Email %email% if you think there has been a mistake. Make sure your channel qualifies %here%.
</I18nMessage>
)}
</span>
}
body={
<section>
{youtubeChannels.map((channel, index) => {
const {
lbry_channel_name: channelName,
channel_claim_id: claimId,
sync_status: syncStatus,
total_subs: totalSubs,
total_videos: totalVideos,
} = channel;
const url = buildURI({ channelName, channelClaimId: claimId });
const transferState = getMessage(channel);
const isWaitingForSync =
syncStatus === YOUTUBE_STATUSES.YOUTUBE_SYNC_QUEUED ||
syncStatus === YOUTUBE_STATUSES.YOUTUBE_SYNC_PENDING ||
syncStatus === YOUTUBE_STATUSES.YOUTUBE_SYNC_PENDING_EMAIL ||
syncStatus === YOUTUBE_STATUSES.YOUTUBE_SYNC_PENDINGUPGRADE ||
syncStatus === YOUTUBE_STATUSES.YOUTUBE_SYNC_SYNCING;
const isNotEligible = syncStatus === YOUTUBE_STATUSES.YOUTUBE_SYNC_ABANDONDED;
return (
<div key={url} className="card--inline">
{claimId ? (
<ClaimPreview
uri={url}
actions={<span className="help">{transferState}</span>}
properties={false}
/>
) : (
<div className="section--padded">
{isNotEligible ? (
<div>{__('%channelName% is not eligible to be synced', { channelName })}</div>
) : (
<div className="progress">
<div className="progress__item">
{__('Claim your handle %handle%', { handle: channelName })}
<Icon icon={ICONS.COMPLETED} className="progress__complete-icon--completed" />
</div>
<div className="progress__item">
{__('Agree to sync')}{' '}
<Icon icon={ICONS.COMPLETED} className="progress__complete-icon--completed" />
</div>
<div className="progress__item">
{__('Wait for your videos to be synced')}
{isWaitingForSync ? (
<Spinner type="small" />
) : (
<Icon icon={ICONS.COMPLETED} className="progress__complete-icon--completed" />
)}
</div>
<div className="help--inline">
{__('Syncing %total_videos% videos from your channel with %total_subs% subscriptions.', {
total_videos: totalVideos,
total_subs: totalSubs,
})}
</div>
<div className="progress__item">
{__('Claim your channel')}
<Icon icon={ICONS.NOT_COMPLETED} className={classnames('progress__complete-icon')} />
</div>
</div>
)}
</div>
)}
</div>
);
})}
{videosImported && (
<div className="section help">{__('%complete% / %total% videos transferred', { complete, total })}</div>
)}
</section>
}
actions={
<>
<div className="section__actions">
{!isYoutubeTransferComplete && (
<Button
button="primary"
disabled={youtubeImportPending || !transferEnabled}
onClick={claimChannels}
label={youtubeChannels.length > 1 ? __('Claim Channels') : __('Claim Channel')}
/>
)}
<Button
button={isYoutubeTransferComplete ? 'primary' : 'link'}
label={__('Explore %SITE_NAME%', { SITE_NAME })}
navigate="/"
/>
</div>
<p className="help">
{youtubeChannels.length > 1
? __('You will be able to claim your channels once they finish syncing.')
: __('You will be able to claim your channel once it has finished syncing.')}{' '}
{youtubeImportPending &&
__('You will not be able to edit the channel or content until the transfer process completes.')}{' '}
<Button button="link" label={__('Learn More')} href="https://lbry.com/faq/youtube#transfer" />{' '}
{addNewChannel && <Button button="link" label={__('Add Another Channel')} onClick={addNewChannel} />}
</p>
</>
}
/>
)
);
}

View file

@ -9,15 +9,9 @@ export const AUTO_GENERATE_THUMBNAIL = 'auto_generate_thumbnail';
export const AUTO_UPDATE_DOWNLOADED = 'auto_update_downloaded';
export const ERROR = 'error';
export const UPGRADE = 'upgrade';
export const EMAIL_COLLECTION = 'email_collection';
export const PHONE_COLLECTION = 'phone_collection';
export const FIRST_REWARD = 'first_reward';
export const AUTHENTICATION_FAILURE = 'auth_failure';
export const TRANSACTION_FAILED = 'transaction_failed';
export const REWARD_GENERATED_CODE = 'reward_generated_code';
export const AFFIRM_PURCHASE = 'affirm_purchase';
export const CONFIRM_CLAIM_REVOKE = 'confirm_claim_revoke';
export const FIRST_SUBSCRIPTION = 'firstSubscription';
export const SEND_TIP = 'send_tip';
export const REPOST = 'repost';
export const CONFIRM_SEND_TIP = 'confirm_send_tip';

Some files were not shown because too many files have changed in this diff Show more